|
| 1 | +--- |
| 2 | +title: 'Deploy NodeJs API on AWS EC2 with docker' |
| 3 | +date: '2021-07-03' |
| 4 | +author: 'Wojciech Cendrzak' |
| 5 | +image: '/images/nodejs-ec2-docker.png' |
| 6 | +tags: 'devops, nodejs, aws, docker' |
| 7 | +isPublished: true |
| 8 | +includeReferences: deploy-nodejs-api-series-section |
| 9 | +--- |
| 10 | + |
| 11 | +In this, we'll cover how to **dockerized** NodeJs application. Finally, we will deploy it on AWS EC2. |
| 12 | + |
| 13 | +{{deploy-nodejs-api-series-section}} |
| 14 | + |
| 15 | +## What Docker is |
| 16 | + |
| 17 | +**Docker** itself is a platform that allows us to manage **containerized** applications. It can run on Linux, Windows, and Mac. |
| 18 | +Linux and Windows support it **natively**. |
| 19 | + |
| 20 | +On Mac, Docker is running through a **lightweight Linux Virtual Machine**. So it is a Docker on Linux under the hood. |
| 21 | +It also exposes Docker API to your Mac environment. Therefore we can use the Docker commands from the Mac terminal in the same way as on the native Linux. |
| 22 | + |
| 23 | +In this tutorial, we will be using Mac. Make sure you have [**Docker Desktop**](https://www.docker.com/products/docker-desktop) installed. |
| 24 | + |
| 25 | +You can check the installation by typing: |
| 26 | + |
| 27 | +```bash |
| 28 | +$ sudo docker version |
| 29 | +``` |
| 30 | + |
| 31 | +The output should looks like this: |
| 32 | + |
| 33 | +```bash |
| 34 | +Client: |
| 35 | + Cloud integration: 1.0.17 |
| 36 | + Version: 20.10.7 |
| 37 | + API version: 1.41 |
| 38 | + Go version: go1.16.4 |
| 39 | + Git commit: f0df350 |
| 40 | + Built: Wed Jun 2 11:56:22 2021 |
| 41 | + OS/Arch: darwin/amd64 |
| 42 | + Context: desktop-linux |
| 43 | + Experimental: true |
| 44 | + |
| 45 | +Server: Docker Engine - Community |
| 46 | + Engine: |
| 47 | + Version: 20.10.7 |
| 48 | + API version: 1.41 (minimum version 1.12) |
| 49 | + Go version: go1.13.15 |
| 50 | + Git commit: b0f5bc3 |
| 51 | + Built: Wed Jun 2 11:54:58 2021 |
| 52 | + OS/Arch: linux/amd64 |
| 53 | + Experimental: false |
| 54 | + containerd: |
| 55 | + Version: 1.4.6 |
| 56 | + GitCommit: d71fcd7d8303cbf684402823e425e9dd2e99285d |
| 57 | + runc: |
| 58 | + Version: 1.0.0-rc95 |
| 59 | + GitCommit: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7 |
| 60 | + docker-init: |
| 61 | + Version: 0.19.0 |
| 62 | + GitCommit: de40ad0 |
| 63 | +``` |
| 64 | + |
| 65 | +## What Container is |
| 66 | + |
| 67 | +**Container** is a piece of software that runs in **isolation** from hosting OS and from other containers. |
| 68 | + |
| 69 | +More precisely, it allows us to **package an application with its environment**, dependencies, and configuration. |
| 70 | + |
| 71 | +Exp, it makes it easy to make development on the local machine. Then deploy it to the cloud without worries that some dependencies will be missing or has a version that our app is not supporting. |
| 72 | + |
| 73 | +It allows us also to run multiple applications that use different versions of a specific library. |
| 74 | +E. g two different versions of the same database engine. It wouldn't be easy without containers. We can achieve that by running multiply Virtual Machines on the host OS. With Docker containers, this is far way easy. |
| 75 | + |
| 76 | +## Isn't the Container a VM itself? |
| 77 | + |
| 78 | +This metaphor is accurate from a usage perspective, but not technically. |
| 79 | +A container provides isolation just like a virtual machine but in a different way. |
| 80 | + |
| 81 | +When running software on **virtual machine**, we need to install the guest OS on it. VM **shares hardware** from the host machine. |
| 82 | + |
| 83 | +In opposition, the container shares not only hardware but **shares a host OS** too. It utilizes container concepts such as **cgroups** and **namespaces**. Those were implemented directly on the Linux kernel and then ported to Windows. So you don't need to install an additional OS. |
| 84 | +It gives an advantage over the virtual machine such as: |
| 85 | + |
| 86 | +- it is very **lightweight**, |
| 87 | +- switching containers on/off is significant **faster** |
| 88 | +- **no additional license** needed for VM guest OS |
| 89 | + |
| 90 | +Docker Container can be from an **Image**. |
| 91 | + |
| 92 | +## What Image is |
| 93 | + |
| 94 | +In simple words, we can say that Docker Image is a **blueprint** for a container. Or a class, for instance. Or container template for a container. We can create a Docker Image through Dockerfile. |
| 95 | + |
| 96 | +## What Dockerfile is |
| 97 | + |
| 98 | +A Dockerfile is a recipe from which we will build an Image. |
| 99 | +It is essentially a **text file** that contains the commands to build an image. |
| 100 | +Those commands are read-only layers, each of which represents a Dockerfile instruction. |
| 101 | +The layers are stacked, and each one is a delta of the changes from the previous layer. |
| 102 | + |
| 103 | +## Complete flow |
| 104 | + |
| 105 | +1. We will create a NodeJs app |
| 106 | +2. We will create a **Dockerfile** |
| 107 | +3. From a Dockerfile we will build an **Image** |
| 108 | +4. We will run an Image into a new **Container** |
| 109 | + |
| 110 | +## 1. Create a NodeJs project |
| 111 | + |
| 112 | +We can create any NodeJs app or clone this git repo: |
| 113 | + |
| 114 | +```bash |
| 115 | +https://github.com/WojciechCendrzak/nestjs-api |
| 116 | +``` |
| 117 | + |
| 118 | +A complete solution is also available here: |
| 119 | + |
| 120 | +```bash |
| 121 | +https://github.com/WojciechCendrzak/nestjs-api/tree/docker |
| 122 | +``` |
| 123 | + |
| 124 | +## 2. Create a Dockerfile |
| 125 | + |
| 126 | +Create a Dockerfile in main folder: |
| 127 | + |
| 128 | +```dockerfile |
| 129 | +# ./Dockerfile |
| 130 | + |
| 131 | +FROM node:14 |
| 132 | + |
| 133 | +WORKDIR /usr/src/app |
| 134 | + |
| 135 | +COPY package*.json ./ |
| 136 | + |
| 137 | +RUN npm install |
| 138 | + |
| 139 | +COPY . . |
| 140 | + |
| 141 | +EXPOSE 3000 |
| 142 | + |
| 143 | +CMD [ "npm", "run", "start" ] |
| 144 | +``` |
| 145 | + |
| 146 | +Let's take a look at the Dockerfile anatomy and break it down, line by line. |
| 147 | +You can check a full specification here [Dockerfile reference](https://docs.docker.com/engine/reference/builder/) |
| 148 | + |
| 149 | +```dockerfile |
| 150 | +FROM node:14 |
| 151 | +``` |
| 152 | + |
| 153 | +In the first line, we can define from which **parent Image** we want to start. It is also possible to create an image from scratch. In that case, we can completely omit **FROM**. But, in most cases, we will base a new Image on another existing one. It simplifies stuff a lot. In this case, we will use 'node:14'. This image comes with NodeJS version 14 LTS and npm installed. It will be taken from [Docker Hub](https://hub.docker.com/_/node) during the build process. |
| 154 | + |
| 155 | +```dockerfile |
| 156 | +WORKDIR /usr/src/app |
| 157 | +``` |
| 158 | + |
| 159 | +Next, we set a working directory for any following Docker instructions. Later we will copy our NodeJs application there. It will be created on the container filesystem if not exists. |
| 160 | + |
| 161 | +```dockerfile |
| 162 | +COPY package*.json ./ |
| 163 | + |
| 164 | +RUN npm install |
| 165 | +``` |
| 166 | + |
| 167 | +In the two following lines, we will install dependencies. First, we copy a package.json and package-lock.json (notice a wildcard \*). What interesting is we are not going to copy all project files. It is because we want to take benefit of **cached Docker layers**. |
| 168 | + |
| 169 | +```dockerfile |
| 170 | +COPY . . |
| 171 | +``` |
| 172 | + |
| 173 | +After that, we want to copy our application source files to the container filesystem. |
| 174 | +The first param stands for source. In this case, a dot means a folder containing Dockerfile. |
| 175 | +The second is a destination inside the container filesystem. In this case, a dot means a WORKDIR that we set up before. |
| 176 | + |
| 177 | +```dockerfile |
| 178 | +EXPOSE 3000 |
| 179 | +``` |
| 180 | + |
| 181 | +Our NesjJs API is bind to port 3000. The EXPOSE instruction informs Docker that the container listens on that port at runtime. |
| 182 | + |
| 183 | +```dockerfile |
| 184 | +CMD [ "npm", "run", "start" ] |
| 185 | +``` |
| 186 | + |
| 187 | +Finally, we will run API by triggering _npm run start_. |
| 188 | +The template for providing this instruction is like that: |
| 189 | + |
| 190 | +```bash |
| 191 | +CMD ["executable","param1","param2"] |
| 192 | +``` |
| 193 | + |
| 194 | +## 3. Buil an Image |
| 195 | + |
| 196 | +Before we build, let create '.dockerignore' file |
| 197 | + |
| 198 | +```bash |
| 199 | +# .dockerignore |
| 200 | + |
| 201 | +node_modules |
| 202 | +npm-debug.log |
| 203 | +``` |
| 204 | + |
| 205 | +It will prevent our local modules and debug logs from being copied onto our Docker image. |
| 206 | + |
| 207 | +Let's navigate the directory with our Dockerfile and run the following command to build the Docker image. |
| 208 | +The **-t** flag lets us tag our image, so it's easier to find later. |
| 209 | + |
| 210 | +```bash |
| 211 | +$ docker build . -t nestjs-api |
| 212 | +``` |
| 213 | + |
| 214 | +Now we can list our new image: |
| 215 | + |
| 216 | +```bash |
| 217 | +$ docker images |
| 218 | + |
| 219 | +REPOSITORY TAG IMAGE ID CREATED SIZE |
| 220 | +nestjs-api latest cb53842aa908 8 seconds ago 1.17GB |
| 221 | +``` |
| 222 | + |
| 223 | +## 4. Run the Container |
| 224 | + |
| 225 | +```bash |
| 226 | +$ docker run -p 3000:3000 -d nestjs-api |
| 227 | +``` |
| 228 | + |
| 229 | +The **-p** flag redirects a public port to a private port inside a container. |
| 230 | +We can also pass **-d** flag, and it will run the container in detached mode. |
| 231 | +We can then close the console leaving the container running in the background. |
| 232 | + |
| 233 | +## Check whether container is running |
| 234 | + |
| 235 | +```bash |
| 236 | +$ curl -i localhost:3000 |
| 237 | + |
| 238 | +HTTP/1.1 200 OK |
| 239 | +X-Powered-By: Express |
| 240 | +Content-Type: text/html; charset=utf-8 |
| 241 | +Content-Length: 12 |
| 242 | +ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE" |
| 243 | +Date: Fri, 02 Jul 2021 23:20:06 GMT |
| 244 | +Connection: keep-alive |
| 245 | +Keep-Alive: timeout=5 |
| 246 | + |
| 247 | +Hello World! |
| 248 | +``` |
| 249 | + |
| 250 | +## Other Docker commands |
| 251 | + |
| 252 | +#### List containers |
| 253 | + |
| 254 | +```bash |
| 255 | +$ docker ps |
| 256 | + |
| 257 | +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
| 258 | +542cd674ae6b nestjs-api "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp suspicious_goldwasser |
| 259 | +``` |
| 260 | + |
| 261 | +#### Prints a logs |
| 262 | + |
| 263 | +```bash |
| 264 | +$ docker logs <container id> |
| 265 | +``` |
| 266 | + |
| 267 | +#### Stop the container |
| 268 | + |
| 269 | +```bash |
| 270 | +$ docker stop <container id> |
| 271 | +``` |
| 272 | + |
| 273 | +#### Go inside running container |
| 274 | + |
| 275 | +```bash |
| 276 | +$ docker exec -it <container id> /bin/bash |
| 277 | +``` |
| 278 | + |
| 279 | +## Deploying on AWS EC2 |
| 280 | + |
| 281 | +Now we will see all the beauty of Docker: |
| 282 | +We can run our dockerized NestJs API on AWS EC2 in the same way as locally. |
| 283 | + |
| 284 | +First login to AWS EC2. |
| 285 | + |
| 286 | +If you haven't yet created any EC2 check [this](/post/deploy-nodejs-api-on-aws-ec2) article. |
| 287 | + |
| 288 | +After connected via SSH, let install first a docker: |
| 289 | + |
| 290 | +```bash |
| 291 | +$ sudo apt install docker.io |
| 292 | +``` |
| 293 | + |
| 294 | +Let's double-check installation: |
| 295 | + |
| 296 | +```bash |
| 297 | +$ sudo docker version |
| 298 | +``` |
| 299 | + |
| 300 | +Clone NestJs API from git and change branch 'docker': |
| 301 | + |
| 302 | +```bash |
| 303 | +$ git clone https://github.com/WojciechCendrzak/nestjs-api |
| 304 | +$ cd nestjs-api |
| 305 | +$ git branch -a |
| 306 | +$ git checkout remotes/origin/docker |
| 307 | +``` |
| 308 | + |
| 309 | +Build and run the container: |
| 310 | + |
| 311 | +```bash |
| 312 | +$ sudo docker build . -t nestjs-api |
| 313 | +$ sudo docker run -p 3000:3000 -d nestjs-api |
| 314 | +``` |
| 315 | + |
| 316 | +Finally, we can check whether it's alive from a local machine: |
| 317 | + |
| 318 | +```bash |
| 319 | +$ curl -i http://ec2-100-24-242-27.compute-1.amazonaws.com:3000 |
| 320 | +``` |
| 321 | + |
| 322 | +Thanks for reading. |
0 commit comments