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: email@example.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:
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
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.
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
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
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.