How to Build and Run Node Applications With Docker and Compose - Kathleen Juell, Developer @ DigitalOcean, @katjuell
● Building containers ● Containerizing for development with Compose ● Containerizing for deployment
Containers
Containers and VMs ● Container features ○ Lightweight ○ Portable ○ Isolated Containers Virtual Machines
1. Building an Image for a Node App
Dockerfile CODE EDITOR FROM node:12-alpine 1. Build the base
Dockerfile CODE EDITOR FROM node:12-alpine RUN apk add --update --no-cache vim 1. Build the base 2. Install container-level dependencies
Dockerfile CODE EDITOR FROM node:12-alpine RUN apk add --update --no-cache curl git vim 1. Build the base 2. Install container-level dependencies
Dockerfile CODE EDITOR FROM node:12-alpine RUN apk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app 1. Build the base 2. Install container-level dependencies 3. Set working directory & user
Dockerfile CODE EDITOR FROM node:12-alpine RUN apk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app COPY package*.json ./ COPY --chown=node:node . . RUN npm install 1. Build the base 2. Install container-level dependencies 3. Set working directory & user 4. Copy code, set permissions, and install project dependencies
Dockerfile CODE EDITOR FROM node:12-alpine RUN apk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app COPY package*.json ./ COPY --chown=node:node . . RUN npm install EXPOSE 8080 CMD [ “node”, “app.js” ] 1. Build the base 2. Install container-level dependencies 3. Set working directory & user 4. Copy code, set permissions, and install project dependencies 5. Expose ports and invoke commands
Up & Running
● Build: docker build -t <docker-demo> . ● Run: docker run --name <docker-demo> -p 80:8080 -d <docker-demo> ● Logs: docker logs <container-id> ● Check: docker ps -a ● Exec: docker exec -it <container-id> sh
Demo Time
2. Development Setup with Compose
A. Prepare the Application
● Service: A running container ● Service definition: Information about how the container will run
● Service: A running container ● Service definition: Information about how the container will run ● 12FA principles to consider: 1. Store config in the environment & separate it from code; 2. Treat backing services as attached resources
Where to look & what to do? ● Where are your database credentials defined? ● Anything else that talks to an attached service?
Before: const mongoose = require('mongoose'); const MONGO_USERNAME = 'sammy'; const MONGO_PASSWORD = 'your_password'; const MONGO_HOSTNAME = '127.0.0.1'; const MONGO_PORT = '27017'; const MONGO_DB = 'sharkinfo'; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_H OSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true});
After: const mongoose = require('mongoose'); const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_H OSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true});
Environment Variables: ● Use .env file
Environment Variables: ● Use .env file MONGO_USERNAME=sammy MONGO_PASSWORD=your_password MONGO_PORT=27017 MONGO_DB=sharkinfo
Environment Variables: ● Use .env file ● Credentials manager (orchestrated environments)
One more stop at `db.js`: . . . const options = { useNewUrlParser: true, reconnectTries: Number.MAX_VALUE, reconnectInterval: 500, connectTimeoutMS: 10000, }; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MO NGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, options).then( function() { console.log('MongoDB is connected'); }) .catch( function(err) { console.log(err); });
And an addition to `package.json`: ... "dependencies": { "ejs": "^2.6.1", "express": "^4.16.4", "mongoose": "^5.4.10" }, "devDependencies": { "nodemon": "^1.18.10" } }
B. Write the Compose File
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs 1. Tell Compose how it should build the app image
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules: delegated 1. Tell Compose how it should build the app image 2. Add environment and volume info
Compose File CODE EDITOR version: '3.4' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules: delegated ports: - "80:8080" command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js 1. Tell Compose how it should build the app image 2. Add environment and volume info 3. Add ports and commands
Compose File CODE EDITOR . . . db: image: mongo:4.2-bionic container_name: db1. Tell Compose what image it should use
Compose File CODE EDITOR . . . db: image: mongo:4.2-bionic container_name: db env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD 1. Tell Compose what image it should use 2. Add environment info
Compose File CODE EDITOR . . . db: image: mongo:4.2-bionic container_name: db env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD volumes: - dbdata:/data/db 1. Tell Compose what image it should use 2. Add environment info 3. Add volumes
Compose File CODE EDITOR . . . volumes: dbdata: node_modules:1. Add top-level volumes key for named volumes
● Run: docker-compose up -d ● List: docker-compose ps ● Logs: docker-compose logs <container> ● Exec: docker-compose exec <container> <command> ● Down: docker-compose down
Demo Time
3. Preparing to Deploy
Compose File TODO CODE EDITOR version: '3.4' services: nodejs: image: <yourDockerHubUsername>/nodejs:v1 container_name: nodejs 1. Application images: Pull from registry
Compose File TODO CODE EDITOR version: '3.4' services: nodejs: image: <yourDockerHubUsername>/nodejs:v1 container_name: nodejs volumes: - app_code:/home/node/app - node_modules:/home/node/app/node_modules 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code
Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name: webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name: webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
Compose File TODO CODE EDITOR certbot: image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - app_code:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name: webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name: webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server 4. Docker for certs: How To Secure a Containerized Node Application with Let's Encrypt
How to Build and Run Node Applications With Docker and Compose - Kathleen Juell, Developer @ DigitalOcean, @katjuell

How To Build and Run Node Apps with Docker and Compose

  • 1.
    How to Buildand Run Node Applications With Docker and Compose - Kathleen Juell, Developer @ DigitalOcean, @katjuell
  • 6.
    ● Building containers ●Containerizing for development with Compose ● Containerizing for deployment
  • 10.
  • 11.
    Containers and VMs ●Container features ○ Lightweight ○ Portable ○ Isolated Containers Virtual Machines
  • 12.
    1. Building anImage for a Node App
  • 13.
  • 14.
    Dockerfile CODE EDITOR FROM node:12-alpine RUNapk add --update --no-cache vim 1. Build the base 2. Install container-level dependencies
  • 15.
    Dockerfile CODE EDITOR FROM node:12-alpine RUNapk add --update --no-cache curl git vim 1. Build the base 2. Install container-level dependencies
  • 16.
    Dockerfile CODE EDITOR FROM node:12-alpine RUNapk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app 1. Build the base 2. Install container-level dependencies 3. Set working directory & user
  • 17.
    Dockerfile CODE EDITOR FROM node:12-alpine RUNapk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app COPY package*.json ./ COPY --chown=node:node . . RUN npm install 1. Build the base 2. Install container-level dependencies 3. Set working directory & user 4. Copy code, set permissions, and install project dependencies
  • 18.
    Dockerfile CODE EDITOR FROM node:12-alpine RUNapk add --update --no-cache curl git vim RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app USER node WORKDIR /home/node/app COPY package*.json ./ COPY --chown=node:node . . RUN npm install EXPOSE 8080 CMD [ “node”, “app.js” ] 1. Build the base 2. Install container-level dependencies 3. Set working directory & user 4. Copy code, set permissions, and install project dependencies 5. Expose ports and invoke commands
  • 19.
  • 20.
    ● Build: dockerbuild -t <docker-demo> . ● Run: docker run --name <docker-demo> -p 80:8080 -d <docker-demo> ● Logs: docker logs <container-id> ● Check: docker ps -a ● Exec: docker exec -it <container-id> sh
  • 21.
  • 22.
  • 23.
    A. Prepare theApplication
  • 24.
    ● Service: Arunning container ● Service definition: Information about how the container will run
  • 25.
    ● Service: Arunning container ● Service definition: Information about how the container will run ● 12FA principles to consider: 1. Store config in the environment & separate it from code; 2. Treat backing services as attached resources
  • 26.
    Where to look& what to do? ● Where are your database credentials defined? ● Anything else that talks to an attached service?
  • 27.
    Before: const mongoose =require('mongoose'); const MONGO_USERNAME = 'sammy'; const MONGO_PASSWORD = 'your_password'; const MONGO_HOSTNAME = '127.0.0.1'; const MONGO_PORT = '27017'; const MONGO_DB = 'sharkinfo'; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_H OSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true});
  • 28.
    After: const mongoose =require('mongoose'); const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_HOSTNAME, MONGO_PORT, MONGO_DB } = process.env; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_H OSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, {useNewUrlParser: true});
  • 29.
  • 30.
    Environment Variables: ● Use.env file MONGO_USERNAME=sammy MONGO_PASSWORD=your_password MONGO_PORT=27017 MONGO_DB=sharkinfo
  • 31.
    Environment Variables: ● Use.env file ● Credentials manager (orchestrated environments)
  • 32.
    One more stopat `db.js`: . . . const options = { useNewUrlParser: true, reconnectTries: Number.MAX_VALUE, reconnectInterval: 500, connectTimeoutMS: 10000, }; const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MO NGO_PORT}/${MONGO_DB}?authSource=admin`; mongoose.connect(url, options).then( function() { console.log('MongoDB is connected'); }) .catch( function(err) { console.log(err); });
  • 33.
    And an additionto `package.json`: ... "dependencies": { "ejs": "^2.6.1", "express": "^4.16.4", "mongoose": "^5.4.10" }, "devDependencies": { "nodemon": "^1.18.10" } }
  • 34.
    B. Write theCompose File
  • 35.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs 1. Tell Compose how it should build the app image
  • 36.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
  • 37.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
  • 38.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules 1. Tell Compose how it should build the app image 2. Add environment and volume info
  • 39.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules: delegated 1. Tell Compose how it should build the app image 2. Add environment and volume info
  • 40.
    Compose File CODE EDITOR version: '3.4' services: nodejs: build: context:. dockerfile: Dockerfile image: nodejs container_name: nodejs env_file: .env environment: - MONGO_HOSTNAME=db volumes: - .:/home/node/app - node_modules:/home/node/app/node_modules: delegated ports: - "80:8080" command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js 1. Tell Compose how it should build the app image 2. Add environment and volume info 3. Add ports and commands
  • 41.
    Compose File CODE EDITOR . .. db: image: mongo:4.2-bionic container_name: db1. Tell Compose what image it should use
  • 42.
    Compose File CODE EDITOR . .. db: image: mongo:4.2-bionic container_name: db env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD 1. Tell Compose what image it should use 2. Add environment info
  • 43.
    Compose File CODE EDITOR . .. db: image: mongo:4.2-bionic container_name: db env_file: .env environment: - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD volumes: - dbdata:/data/db 1. Tell Compose what image it should use 2. Add environment info 3. Add volumes
  • 44.
    Compose File CODE EDITOR . .. volumes: dbdata: node_modules:1. Add top-level volumes key for named volumes
  • 45.
    ● Run: docker-composeup -d ● List: docker-compose ps ● Logs: docker-compose logs <container> ● Exec: docker-compose exec <container> <command> ● Down: docker-compose down
  • 46.
  • 47.
  • 48.
    Compose File TODO CODE EDITOR version: '3.4' services: nodejs: image:<yourDockerHubUsername>/nodejs:v1 container_name: nodejs 1. Application images: Pull from registry
  • 49.
    Compose File TODO CODE EDITOR version: '3.4' services: nodejs: image:<yourDockerHubUsername>/nodejs:v1 container_name: nodejs volumes: - app_code:/home/node/app - node_modules:/home/node/app/node_modules 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code
  • 50.
    Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name:webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
  • 51.
    Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name:webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
  • 52.
    Compose File TODO CODE EDITOR certbot: image: certbot/certbot container_name:certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - app_code:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com -d www.example.com 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
  • 53.
    Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name:webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server
  • 54.
    Compose File TODO CODE EDITOR webserver: image: nginx:1.17-alpine container_name:webserver ports: - "80:80" volumes: - app_code:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs 1. Application images: Pull from registry 2. Use Named Volumes: Prefer them over bind mounts for app code 3. Add a web server 4. Docker for certs: How To Secure a Containerized Node Application with Let's Encrypt
  • 55.
    How to Buildand Run Node Applications With Docker and Compose - Kathleen Juell, Developer @ DigitalOcean, @katjuell