├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── index.js ├── package.json └── scripts ├── bootstrap.sh └── terminal.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Node.js version 2 | FROM node:6.9.2 3 | 4 | # Create app directory 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | 8 | # get the npm modules that need to be installed 9 | COPY package.json /usr/src/app/ 10 | 11 | # install npm modules 12 | RUN npm install 13 | 14 | # copy the source files from host to container 15 | COPY . /usr/src/app -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 shaunpersad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-tutorial 2 | A simple intro to Docker with Node.js 3 | 4 | 5 | ## How to use this tutorial 6 | 7 | The files in this project represents the end result of this tutorial, so please create your own project, and use this 8 | repository as a final check-against. 9 | 10 | ## Goals 11 | - To familiarize ourselves with Docker concepts and basics 12 | - To build a basic app that runs in a Docker container 13 | 14 | 15 | ## Docker? I hardly know her! 16 | 17 | (Warning: I am not a Docker expert) 18 | 19 | Docker is a container service - a way of building and running isolated environments. You may be used to working with Vagrant to build, provision, and use VMs for individual projects. Docker can replace Vagrant in those duties. For our intents and purposes, a container is similar to a virtual machine, but not quite the same. 20 | 21 | If none of this is familiar to you, the gist is that containers and/or VMs are isolated environments that can run applications inside of them. 22 | 23 | For example, you can build yourself a Node.js-capable environment with Postgres and Elasticsearch without ever having to install any of those technologies directly on your local machine. They, and your application, will run inside the isolated environment. 24 | 25 | This is a very powerful concept, as these environments are repeatable and shareable, meaning that your entire team, your staging server, and your production server, can all use the same environments: 26 | the same OS, the same versions of languages and tech stacks, etc. 27 | 28 | For Docker specifically, the concepts of *images* and *containers* are used. Images are the blueprints for creating containers. Containers are running instances of an image. 29 | 30 | For example, if I download the Node image, I can run a container based off of that image. As an OOP analogy, images are the classes, and containers are the individual instances of a particular class. 31 | 32 | You can learn more about Docker here: https://docs.docker.com/engine/getstarted/ 33 | 34 | 35 | ## Some Docker concepts 36 | 37 | Before we get into the tutorial and start using Docker, there are some more concepts that we should familiarize ourselves with, particularly 38 | the relationships between *Docker*, *Dockerfiles*, *Docker Compose*, and how they in turn relate to the concepts of *images* and *containers*. 39 | 40 | ### Dockerfile => Image => Container 41 | 42 | When you create a new project that you want to use Docker for, the first thing you'll want to do is create a `Dockerfile` file, in a similar idea to how you'd create a gulpfile or gruntfile. 43 | 44 | In it, you will specify the details of the environment you wish to create. **A Dockerfile is the spec used to create an image.** 45 | 46 | Normally in your Dockerfile, you would specify a base image (like Ubuntu, Node, etc.) to extend from, and then add in your application-specific requirements. 47 | 48 | Once your Dockerfile is complete, you can then use it to build an image, and then use that image to run a container instance of that image. 49 | 50 | Basically, Dockerfiles are used to create images, which in turn create containers. 51 | 52 | Dockerfile => Shareable image based on Dockerfile => run instances of the image as containers. 53 | 54 | Read more about Dockerfiles and the image creation process here: https://docs.docker.com/engine/getstarted/step_four/ 55 | 56 | ### Docker vs. Docker Compose 57 | 58 | Generally, when you want to spin up a container from an image, you will use Docker. However, there is also another program called Docker Compose that can also do that and more. 59 | 60 | **Docker Compose allows you to spin up containers from many different images that can all talk to each other and act as a single application stack/ecosystem.** 61 | 62 | For example, you can have an application server based on a Node image, a database server based on a MySQL image, and a redis server based on a Redis image. 63 | 64 | Instead of spinning these up individually using Docker, you can use Docker Compose to spin them all up at once, with the added benefit of allowing each running container to connect to the other automatically. 65 | 66 | Docker Compose accomplishes this orchestration via the use of a `docker-compose.yml` file. 67 | 68 | In this file, you specify the images you wish to use, and some config for each image, including which images depend on and can talk to which other images. 69 | 70 | You can then run this setup via Docker Compose. 71 | 72 | As such, you generally want to use Docker Compose when considering a microservices architecture, and Docker when considering a monolithic architecture. 73 | 74 | 75 | ## Getting started: 76 | 77 | ### Step 1 - Prereqs. 78 | 79 | Download [Docker](https://docs.docker.com/docker-for-mac/) and create a new project directory and repository. 80 | 81 | Please note the system requirements: https://docs.docker.com/docker-for-mac/#/what-to-know-before-you-install 82 | 83 | Basically, if you're on Mac (which I'm assuming), you should have OS X El Capitan 10.11 or higher, or you may get weird bugs. 84 | 85 | Next, set up a `docker-tutorial` directory to house your tutorial project. 86 | 87 | ### Step 2 - Dockerfile 88 | 89 | Create a Dockerfile, like the one in this project: 90 | ```Dockerfile 91 | # Node.js version 92 | FROM node:6.9.2 93 | 94 | # Create app directory 95 | RUN mkdir -p /usr/src/app 96 | WORKDIR /usr/src/app 97 | 98 | # get the npm modules that need to be installed 99 | COPY package.json /usr/src/app/ 100 | 101 | # install npm modules 102 | RUN npm install 103 | 104 | # copy the source files from host to container 105 | COPY . /usr/src/app 106 | ``` 107 | The above Dockerfile does the following: 108 | - Builds from the official existing Node.js image 109 | - This image has its own OS, which differs depending on which version you select to build from. 110 | - More info here: https://hub.docker.com/_/node/ 111 | - Creates an app directory 112 | - Copies package.json and runs `npm install` from it 113 | - Copies your app source files to the container 114 | 115 | To see the full extent of what Dockerfiles can do, you may want to browse through the [documentation](https://docs.docker.com/engine/reference/builder/). 116 | 117 | ### Step 3 - .dockerignore 118 | 119 | Create a `.dockerignore` file, like the one in this project: 120 | ``` 121 | node_modules 122 | npm-debug.log 123 | ``` 124 | 125 | Much like .gitignore, .dockerignore ignores files when copying from your host to the container. In this case, we don't want to copy any of the logs generated by node, or the node_modules from the host. 126 | 127 | ### Step 4 - Generate a `package.json` file 128 | 129 | Notice that the Dockerfile specifies to copy `package.json` into the container. However, we don't yet have one, so attempting to build this image will fail. 130 | 131 | To get a `package.json` file, you must run `npm init`, but, since the whole point of containers is to *not* install environments on your host machine, how will we run `npm init` without having Node or NPM locally? 132 | 133 | The answer is to run the official Node.js image as a container in order to create a `package.json` file, by linking your host directory to a directory in the container. 134 | In doing so, when you create the `package.json` file in the container, it will automatically show up in your host directory as well. 135 | 136 | Alternatively, you can manually create one, but you won't get the benefits of it being generated for you. 137 | 138 | #### Create a terminal 139 | 140 | To generate the `package.json` file, we need to create an interactive terminal into a container that has node: 141 | ```bash 142 | docker run -it --rm -v $(pwd):/usr/src/app -w /usr/src/app node:6.9.2 /bin/bash 143 | ``` 144 | That's quite the mouthful, so here's the breakdown of how we get to that: 145 | - `docker run node:6.9.2` - run a container from the official node image 146 | - `docker run node:6.9.2 /bin/bash` - run a terminal in the node container 147 | - `docker run -it --rm node:6.9.2 /bin/bash` - "-it" makes an interactive terminal, "--rm" means the container will clean itself up after we're done with it 148 | - finally, we add `-v $(pwd):/usr/src/app -w /usr/src/app` in there, which creates a volume from our host machine from `$(pwd)` (the current directory) to `/usr/src/app` in the container, and then changes the working directory to `/usr/src/app` 149 | 150 | From the above, we've learned that you can use `docker run` to run images, and they can be used to do temporary things. 151 | To learn more about `docker run`, read here: https://docs.docker.com/engine/reference/run/ 152 | To learn more about how to link directories from your host to docker containers, read here: https://docs.docker.com/engine/tutorials/dockervolumes/ 153 | 154 | #### Use the terminal to create the package.json 155 | 156 | Now that we have a running terminal, we can run `npm init`. Once done with the setup, type `exit` to exit the container. 157 | 158 | As an alternative to creating the bash terminal and using it to run `npm init`, we could run the following: 159 | ```bash 160 | docker run --rm -v $(pwd):/usr/src/app -w /usr/src/app node:6.9.2 npm init --yes 161 | ``` 162 | The above one-liner automatically runs `npm init` in the container, and also supplies the `--yes` flag to skip the setup process. 163 | 164 | For your convenience, I have saved both commands in the `scripts` directory: 165 | - `terminal.sh` can be used when you wish to create a node terminal. 166 | - This will also be useful when adding new packages with `npm install {package} -- save` 167 | - `bootstrap.sh` can be used to automatically generate a `package.json` file when you start a new project. 168 | 169 | ### Step 5 - Your source code 170 | 171 | To build an app, we need code! Let's make it simple by creating an `index.js` file that contains the following: 172 | ```js 173 | console.log('Hello world!'); 174 | ``` 175 | 176 | ### Step 6 - Build your image 177 | 178 | Now that we have everything the Dockerfile requires, we can build our image! 179 | 180 | We can use either Docker or Docker Compose to do this. Both ways will be described. 181 | 182 | The end result of either way will be a runnable image called `shaunpersad/docker-tutorial`. 183 | 184 | #### 6A - The regular Docker way 185 | 186 | Run the following command: 187 | ```bash 188 | docker build -t shaunpersad/docker-tutorial . 189 | ``` 190 | **Don't forget that last dot!** 191 | - the "-t" flag tags the image with "shaunpersad/docker-tutorial" name. 192 | - the "." specifies the directory of the Dockerfile to use. 193 | - Don't mind the red outputs when NPM runs...it does that. 194 | 195 | #### 6B - The Docker Compose way 196 | 197 | Create a `docker-compose.yml` file. In it, we will specify a single *service*: 198 | ```yaml 199 | version: '2' 200 | services: 201 | app: 202 | build: . 203 | image: shaunpersad/docker-tutorial 204 | command: node index.js 205 | ``` 206 | The above file has the following properties: 207 | - `version`: This lets Docker Compose know which format the YAML file is in 208 | - `services`: Here is where we define the containers we want to run 209 | - `app`: The name of our app service. There's nothing special about this name. You can call it "the-bunny-ranch" if you wanted to. 210 | - `build`: The directory that contains our `Dockerfile` 211 | - `image`: The name of the image we'll create when we build. If this was specified without a `build` property, Docker Compose would have attempted to pull that image from [Docker Hub](https://hub.docker.com/explore/), a public repository of images. 212 | - `command`: The command to run in the container once it's running 213 | Now run the following: 214 | ```bash 215 | docker-compose build 216 | ``` 217 | 218 | ### Step 7 - Run your image as a container instance 219 | 220 | Now that our image has been built, we can run an instance of it as a container. If successful, it will run `index.js`, 221 | which will print "Hello World!" in the console. 222 | 223 | #### 7A - the regular Docker way 224 | 225 | Regardless of if you used Docker or Docker Compose in Step 6, you can still use just Docker to run the built image, since both ways resulted in the creation of a referencable image called `shaunpersad/docker-tutorial`. 226 | 227 | Run the following command: 228 | ```bash 229 | docker run shaunpersad/docker-tutorial node index.js 230 | ``` 231 | Notice the `node index.js` that you'd normally run if you were in a node environment. 232 | 233 | Alternatively, we could have stuck this command to run automatically, if we specified a `CMD` at the end of our Dockerfile: 234 | ```bash 235 | CMD [ "node", "index.js" ] 236 | ``` 237 | When the command is run, you'll see `Hello World!` output in the terminal. 238 | 239 | Also notice that the app exited immediately. This is the typical behavior of a node app that is not expecting any I/O. 240 | If we were building an express app or some other app that listened to a port, the container would have stayed up. 241 | 242 | #### 7B - the Docker Compose way 243 | 244 | Run the following command: 245 | ```bash 246 | docker-compose up 247 | ``` 248 | You should see something similar to this output in the terminal: 249 | ```bash 250 | app_1 | Hello world! 251 | ``` 252 | "app_1" is simply a tag for the service we are running. It's necessary because with Docker Compose, we can run several services at once. 253 | 254 | Note that with steps 6B and 7B, we could've skipped the `docker-compose build` step, and went directly to `docker-compose up`, 255 | which would've automatically built the image first before running. 256 | 257 | 258 | ## Congratulations! 259 | 260 | You've just successfully run your first app in Docker. Try modifying the contents of `index.js`, rebuilding the image, and rerunning the container. 261 | 262 | If that sounds like a chore, that's because it is, especially as your app grows in size and the `COPY` stage gets longer. 263 | 264 | As it stands right now, each time you add or modify your source files, you'll need to rebuild the image and rerun the container. 265 | 266 | In Part 2, we will explore how to get around this so that your app updates automatically when you modify the source code. 267 | 268 | We will also get more in-depth with Docker Compose and how to use it to build more complex environments. 269 | 270 | Head to [Part 2](https://github.com/shaunpersad/docker-tutorial-pt2) now! 271 | 272 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | app: 4 | build: . 5 | image: shaunpersad/docker-tutorial 6 | command: node index.js -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world!'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "A simple intro to Docker with Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/shaunpersad/docker-tutorial.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/shaunpersad/docker-tutorial/issues" 18 | }, 19 | "homepage": "https://github.com/shaunpersad/docker-tutorial#readme" 20 | } 21 | -------------------------------------------------------------------------------- /scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker run --rm -v $(pwd):/usr/src/app -w /usr/src/app node:6.9.2 npm init --yes -------------------------------------------------------------------------------- /scripts/terminal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker run -it --rm -v $(pwd):/usr/src/app -w /usr/src/app node:6.9.2 /bin/bash 3 | --------------------------------------------------------------------------------