CodeDevStack

Setting up Docker compose with NodeJS, Nginx, Postgres and Let's encrypt

February 08, 2020

Some random code

A few months ago I wanted to create a docker-compose file for a project of mine. It used NodeJS, setup Nginx as a reverse proxy, postgres for a database and Let’s encrypt for free certificates. After a little bit of research I ended up with something like this:

version: "3"
services:
  web:
    restart: always
    image: #docker image of the node app
    build: .
    container_name: my-app
    command: npm start #or node index.js, etc
    depends_on:
      - postgres
    environment:
      DATABASE_URL: postgres://my-db@postgres/my-db
      NODE_ENV: production
      VIRTUAL_HOST: api.codedevstack.com
      LETSENCRYPT_HOST: api.codedevstack.com
      LETSENCRYPT_EMAIL: myemail@gmail.com
  postgres:
    restart: always
    image: postgres:9.6.2-alpine
    container_name: my-pg-db
    volumes:
      - my-app-data:/var/lib/postgresql/data
    ports:
      - "0.0.0.0:5432:5432"
    environment:
      POSTGRES_USER: my-db
      POSTGRES_DB: my-db
  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - static:/app/app/static/
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock:ro
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
    depends_on:
      - web
  letsencrypt:
    restart: always
    container_name: letsencrypt
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - nginx-proxy
volumes:
  my-app-data:
  html:
  certs:
  vhost:
  static:

Let’s go step by step:

Web

This is the actual app which exposes the API. This docker image uses mhart/alpine-node:13.8 as a base and then copies the app into it and install dependencies.

FROM mhart/alpine-node:13.8

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install --only=production

# Copy the actual app's code
COPY . .

EXPOSE 3000

Postgres

An Alpine image postgres:9.6.2-alpine for starting up a Postgres database. We are creating a volume for it too, so data is not lost when the container is restarted.

Nginx-proxy

Another Alpine image, this time for Nginx as a proxy. Note how we are mapping the ports for normal http traffic on port 80 and encrypted traffic on port 443. We also setup different volumes so we can share data from other containers, for example, the certificates. This specific image picks up the exposed port of the depends_on container and uses it for it’s reverse proxying. In this case, port 3000.

Letsencrypt

This image just uses the configuration data from the docker-compose file and set’s up the certificates. It also manages renewing the certificates automatically.

After setting this up I created a Digital Ocean instance with docker installed and then, all I needed to do, was run the command docker-compose up.

This would download the images, start the containers in the correct order, get the certificates and that’s it! You have a NodeJS app up and running, behind an Nginx server using Let’s encrypt certs and let’s not forget about the postgres database with permanent data saved to a volume outside of the container.All in all, a pretty good initial setup for an MVC or small project.