├── .dockerignore ├── .nginx └── nginx.conf ├── src ├── client │ ├── index.html │ └── index.js └── server │ └── index.js ├── LICENSE ├── package.json ├── Dockerfile ├── Dockerfile.client ├── .gitignore └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | node_modules 4 | ./src/server/data/messages.json 5 | ./messages.json 6 | -------------------------------------------------------------------------------- /.nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | root /var/www; 7 | 8 | location / { 9 | try_files $uri $uri/ $uri.html =404; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Frontend 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const res = await fetch('http://localhost:3000'); 3 | const data = await res.json(); 4 | 5 | const app = document.getElementById('app'); 6 | 7 | const ul = document.createElement('ul'); 8 | app.appendChild(ul); 9 | data.messages.forEach((item) => { 10 | const li = document.createElement('li'); 11 | li.innerHTML = item.name; 12 | ul.appendChild(li); 13 | }); 14 | 15 | const button = document.createElement('button'); 16 | button.innerHTML = 'Create New Message'; 17 | app.appendChild(button); 18 | button.addEventListener('click', async () => { 19 | const res = await fetch('http://localhost:3000', { 20 | method: 'POST', 21 | }); 22 | const data = await res.json(); 23 | ul.innerHTML = ''; 24 | data.messages.forEach((item) => { 25 | const li = document.createElement('li'); 26 | li.innerHTML = item.name; 27 | ul.appendChild(li); 28 | }); 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Lukas Aichbauer, MSc (https://devopscycle.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-ultimate-docker-cheat-sheet", 3 | "version": "0.1.0", 4 | "description": "Learn Docker", 5 | "main": "./src/server.js", 6 | "scripts": { 7 | "cleanup": "rm -rf node_modules && rm -rf build && rm -rf .cache && rm -rf .parcel-cache && rm -rf dist", 8 | "comment": "echo \"All build and start commands are only example commands and would never be used like this in a procution environment\"", 9 | "start:server": "node ./src/server/index.js", 10 | "build:server": "mkdir -p ./build && cp ./src/server/index.js ./build", 11 | "start:client": "mkdir -p ./dist && cp ./src/client/index.js ./dist && cp ./src/client/index.html ./dist && serve ./dist -p 80", 12 | "build:client": "mkdir -p ./dist && cp ./src/client/index.js ./dist && cp ./src/client/index.html ./dist" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+ssh://git@github.com/aichbauer/the-ultimate-docker-cheat-sheet.git" 17 | }, 18 | "keywords": [ 19 | "docker cheat sheet" 20 | ], 21 | "author": "Lukas Aichbauer, MSc (https://devopscycle.com)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/aichbauer/the-ultimate-docker-cheat-sheet/issues" 25 | }, 26 | "homepage": "https://github.com/aichbauer/the-ultimate-docker-cheat-sheet#readme", 27 | "dependencies": { 28 | "@fastify/cors": "^8.4.1", 29 | "fastify": "^4.24.3" 30 | }, 31 | "devDependencies": { 32 | "serve": "^14.2.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify'); 2 | const cors = require('@fastify/cors'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const app = fastify({ 7 | logger: true, 8 | }); 9 | const port = 3000; 10 | const host = '0.0.0.0'; 11 | 12 | app.register(cors); 13 | 14 | app.get('/', () => { 15 | try { 16 | const messages = fs.readFileSync(path.join(__dirname, 'data', 'messages.json')); 17 | 18 | return JSON.parse(messages); 19 | } catch { 20 | return { 21 | messages: [ 22 | { name: 'Hello World' }, 23 | ], 24 | }; 25 | } 26 | }); 27 | 28 | app.post('/', () => { 29 | try { 30 | if (!fs.existsSync(path.join(__dirname, 'data'))) { 31 | fs.mkdirSync(path.join(__dirname, 'data'), { recursive: true }); 32 | } 33 | 34 | if (!fs.existsSync(path.join(__dirname, 'data', 'messages.json'))) { 35 | fs.writeFileSync(path.join(__dirname, 'data', 'messages.json'), JSON.stringify({ 36 | messages: [ 37 | { name: 'Hello World' }, 38 | ], 39 | })); 40 | } 41 | 42 | const messages = fs.readFileSync(path.join(__dirname, 'data', 'messages.json')); 43 | 44 | const parsedMessages = JSON.parse(messages); 45 | 46 | parsedMessages.messages.push({ 47 | name: 'New Message', 48 | }); 49 | 50 | fs.writeFileSync(path.join(__dirname, 'data', 'messages.json'), JSON.stringify(parsedMessages)); 51 | 52 | return parsedMessages; 53 | } catch (err) { 54 | console.error(err); 55 | return { 56 | messages: [ 57 | { name: 'Hello World' }, 58 | ], 59 | }; 60 | } 61 | }); 62 | 63 | 64 | const start = async () => { 65 | try { 66 | app.log.info(`Server listening on port ${port}`); 67 | await app.listen({ 68 | port, 69 | host, 70 | }); 71 | } catch (err) { 72 | app.log.error(err); 73 | process.exit(1); 74 | } 75 | } 76 | start() 77 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # the next line sets the base image for this image 2 | # the base image is also based on an Dockerfile 3 | # see: https://hub.docker.com/layers/library/node/18-alpine/images/sha256-a0b787b0d53feacfa6d606fb555e0dbfebab30573277f1fe25148b05b66fa097 4 | # node provides offical images for nodejs and alpine 5 | # is used as a lightweight linux distribution 6 | # to reduce image size 7 | FROM node:18-alpine 8 | 9 | # we update the package list so that we always install 10 | # the latest version of a package 11 | RUN apk update && apk upgrade --no-cache 12 | # we install tini to have a proper init process 13 | # see: https://github.com/krallin/tini 14 | # this is needed to properly handle signals 15 | # like SIGTERM and SIGINT to stop and kill the container 16 | RUN apk add --no-cache tini 17 | # Tini is now available at /sbin/tini 18 | ENTRYPOINT ["/sbin/tini", "--"] 19 | 20 | # sets the working directory inside the image 21 | # all commands after this insturction will be 22 | # executed inside this directory 23 | WORKDIR /app 24 | 25 | # copies the package.json and package-lock.json 26 | # from the client (e.g. your server or your development machine) 27 | # into the /app directory inside the image 28 | # this is done before running npm ci to 29 | # get the advantage of layer caching 30 | COPY ./package* . 31 | 32 | # installs all node.js dependencies 33 | # npm ci is similar to npm install but intended to be 34 | # used in continuous integration (CI) environments 35 | # it will do a clean install based on the package-lock.json 36 | RUN npm ci 37 | 38 | # copies the source code into the image 39 | COPY . . 40 | 41 | # this runs the build command specified in the package.json 42 | RUN npm run build:server 43 | 44 | # does nothing 45 | # this is documentation so that we know which port is used for that image 46 | EXPOSE 3000 47 | 48 | # executes the server.js file that is located in the build directory 49 | CMD ["node", "./build/index.js"] 50 | -------------------------------------------------------------------------------- /Dockerfile.client: -------------------------------------------------------------------------------- 1 | # the base image 2 | # name it builder 3 | # you can reference this stage 4 | # in other stages by this name 5 | FROM node:18-alpine as builder 6 | 7 | # working directory inside the image 8 | WORKDIR /app 9 | 10 | # copies files from the client to the image 11 | COPY ./package* . 12 | 13 | # run a command inside the image 14 | RUN npm ci 15 | 16 | # copies files from the client to the image 17 | COPY . . 18 | 19 | # run a command inside the container 20 | # this will create a new folder in called dist in our app directory 21 | # inside the dist directory, you will find the 22 | # final HTML and JavaScript file 23 | RUN npm run build:client 24 | 25 | # serve stage 26 | # unprivileged nginx base image named as serve 27 | # will start nginx as non root user 28 | FROM nginxinc/nginx-unprivileged:1.24 as serve 29 | 30 | # we can now copy things from the first stage to the second 31 | # we copy the build output to the directory where nginx serves files 32 | COPY --from=builder /app/dist /var/www 33 | 34 | # we overwrite the default config with our own 35 | # if you take a look at the GitHub repository, you 36 | # see the .nginx directory with the nginx.conf 37 | # here we only use the port 80 38 | # in production, you would also want to make sure 39 | # all requests, even in your internal network or Kubernetes cluster 40 | # is served via HTTPS when dealing with sensible data 41 | COPY --from=builder /app/.nginx/nginx.conf /etc/nginx/conf.d/default.conf 42 | 43 | # this instruction does not really expose the port 80 44 | # this is documentation so that we know which port is used for that image 45 | # we nee to expose this port via the --publish flag when running the container 46 | EXPOSE 80 47 | 48 | # The command used when the image is started as a container 49 | # Note: for Docker containers (or for debugging), 50 | # the "daemon off;" directive which is used in this example 51 | # tells nginx to stay in the foreground. 52 | # for containers, this is useful. 53 | # best practice: one container = one process. 54 | # one server (container) has only one service. 55 | CMD ["nginx", "-g", "daemon off;"] 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/server/data/messages.json 2 | messages.json 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | # MAC OS 136 | # General 137 | .DS_Store 138 | .AppleDouble 139 | .LSOverride 140 | 141 | # Icon must end with two \r 142 | Icon 143 | 144 | # Thumbnails 145 | ._* 146 | 147 | # Files that might appear in the root of a volume 148 | .DocumentRevisions-V100 149 | .fseventsd 150 | .Spotlight-V100 151 | .TemporaryItems 152 | .Trashes 153 | .VolumeIcon.icns 154 | .com.apple.timemachine.donotpresent 155 | 156 | # Directories potentially created on remote AFP share 157 | .AppleDB 158 | .AppleDesktop 159 | Network Trash Folder 160 | Temporary Items 161 | .apdisk 162 | 163 | # WINDOWS 164 | # Windows thumbnail cache files 165 | Thumbs.db 166 | Thumbs.db:encryptable 167 | ehthumbs.db 168 | ehthumbs_vista.db 169 | 170 | # Dump file 171 | *.stackdump 172 | 173 | # Folder config file 174 | [Dd]esktop.ini 175 | 176 | # Recycle Bin used on file shares 177 | $RECYCLE.BIN/ 178 | 179 | # Windows Installer files 180 | *.cab 181 | *.msi 182 | *.msix 183 | *.msm 184 | *.msp 185 | 186 | # Windows shortcuts 187 | *.lnk 188 | 189 | # LINUX 190 | *~ 191 | 192 | # temporary files which can be created if a process still has a handle open of a deleted file 193 | .fuse_hidden* 194 | 195 | # KDE directory preferences 196 | .directory 197 | 198 | # Linux trash folder which might appear on any partition or disk 199 | .Trash-* 200 | 201 | # .nfs files are created when an open file is removed but is still being accessed 202 | .nfs* 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # The Ultimate Docker Cheat Sheet 8 | 9 | > Learn Docker 10 | 11 | The content of this repository can guide you to learn to use Docker. Here is the accompanying blog article: [The Ultimate Docker Cheat Sheet](https://devopscycle.com/blog/the-ultimate-docker-cheat-sheet) with the [PDF](https://devopscycle.com/wp-content/uploads/sites/4/2023/12/the-ultimate-docker-cheat-sheet-1.pdf) or an [image](https://devopscycle.com/wp-content/uploads/sites/4/2023/11/the-ultimate-docker-cheat-sheet-4.png) to the Docker Cheat Sheet. This repository is intended for corporate trainings, university courses and all people (mainly developers and system administrators, but also QA, security experts) that are interested into learning DevOps and especially in automating their processes and tasks to improve the iteration speed, the quality of their work output, and the overall transparancy in their company. 12 | 13 | ## Why? 14 | 15 | It is hard getting started with the technical implementation of DevOps tools. Sharing Knowledge is an important part in DevOps and this is why this repository exists. This repository should give you some guidance on how you can start. This is by no means a silver bullet and also never finished. Another important part is continuous imporvement. You could use this repository as entrypoint for an internal hackathon at your company or your university. Feel free to share your results and learnings as a pull request to this repository. 16 | 17 | Before you start with automating the product lifecycle and implementation of DevOps tools, you should have the correct foundation. 18 | 19 | Start with the culture and the mindset. 20 | 21 | You get a slighty different definition for DevOps when you look at different websites, but the intersection is always culture or the cultural philosophy. So get the key principles straight, then you will be able to profit from the technical tools as well: 22 | 23 | * Colloboration & Communication 24 | * Continuous Improvement 25 | * Automation of the Product Lifecycle 26 | * Customer Centric Action & Short Feedback Loops 27 | 28 | Here are some good resources to get started with colloboration, communication and continuous imporvment: 29 | 30 | * [https://dora.dev/devops-capabilities/cultural/generative-organizational-culture/](https://dora.dev/devops-capabilities/cultural/generative-organizational-culture/) 31 | * [https://dora.dev/devops-capabilities/cultural/learning-culture/](https://dora.dev/devops-capabilities/cultural/learning-culture/) 32 | 33 | ## Prerequistits 34 | 35 | * [Docker](https://docs.docker.com/) 36 | * [Node.js](https://nodejs.org/) 37 | 38 | ## Apps 39 | 40 | In this section you will get an overview of the applications in this repository. 41 | 42 | ### Client 43 | 44 | The client application consits of a HTML and a JS file. The app makes a HTTP Get request to the server application to get all messages and displays them in an unordered list. The App has also an button, which you can click to make a HTTP POST request to add a new message on the server. The server responds with all messages and the client replaces the unordered list items with the new ones. The client is located at `./src/client`. 45 | 46 | ## Development 47 | 48 | In this section you will get an overview of how you can start the client in development. 49 | 50 | ### Start 51 | 52 | ```sh 53 | # start development 54 | # you need to cancel and restart this cmd 55 | # if you want to see changes that you make to the client 56 | $ npm run start:client 57 | ``` 58 | 59 | ## Production 60 | 61 | In this section you will get an overview of how you can start the client in production. 62 | 63 | ### Build 64 | 65 | ```sh 66 | # we use Docker to build the images from the Dockerfile.client 67 | # make sure you are in the directory where the Dockerfile.client is located 68 | $ docker build --file Dockerfile.client --tag examplename/examplerepository-client:0.1.0 . 69 | ``` 70 | 71 | ### Start 72 | 73 | ```sh 74 | # we can now run the image and publish the container port 80 to the host machine port 80 75 | $ docker run --rm --publish 80:80 examplename/examplerepository-client:0.1.0 76 | ``` 77 | 78 | ### Server 79 | 80 | The server consists of a single JS file. It hosts a simple Fastify app with two routes `GET /` and `POST /`. 81 | The GET route will try to load a json file if it exists and reponds with the JSON. the POST route will create a json file if it does not exists and add a new message to the file. The server app is located in `./src/server` directory. 82 | 83 | The json file is located in `./src/server/data` and is called `messages.json`. 84 | 85 | The messages JSON looks like: 86 | 87 | ```json 88 | { 89 | "messages": [ 90 | { 91 | "name": "Hello World" 92 | } 93 | ] 94 | } 95 | ``` 96 | 97 | ## Development 98 | 99 | In this section you will get an overview of how you can start the server in development. 100 | 101 | ### Start 102 | 103 | ```sh 104 | # start development 105 | # you need to cancel and restart this cmd 106 | # if you want to see changes that you make to the server 107 | $ npm run start:server 108 | ``` 109 | 110 | ## Production 111 | 112 | In this section you will get an overview of how you can start the server in production. 113 | 114 | ### Build 115 | 116 | ```sh 117 | # we use Docker to build the images from the Dockerfile.client 118 | # make sure you are in the directory where the Dockerfile is located 119 | $ docker build --file Dockerfile --tag examplename/examplerepository-server:0.1.0 . 120 | ``` 121 | 122 | ### Start 123 | 124 | ```sh 125 | # we can now run the image and publish the container port 3000 to the host machine port 3000 126 | # we also persist the messages.json throughout container starts in a volume called server-volume 127 | $ docker run --rm --volume server-volume:/app/build/data --publish 3000:3000 examplename/examplerepository-server:0.1.0 128 | ``` 129 | 130 | ## LICENSE 131 | 132 | MIT @ Lukas Aichbauer 133 | --------------------------------------------------------------------------------