Back to top

Virtual Private Server

Setting up a server and deploying microservices with Docker Swarm

Overview

My website, eCommerce store, and other services are all hosted on a Linux VPS. It consists of microservices deployed with Docker Swarm in order to make development and updates easier. Even though I only have one server I’m using Docker Swarm for some of its features and to make future expansion easier.

Server Pain

After lots of research, I decided to bring up a server in order to host an eCommerce store using Magento, several knowledge bases using BookStack, Phabricator for projects, and my website/landing page.

I tried installing and testing everything locally but quickly realized how awful it was going to be to keep my local and remote server in sync and updated, plus all the other usual reasons why people decide to use containers and microservices. So I started to work on setting up containers for everything. I decided to use Docker Swarm for some of the features it has and because it’s a bit easier to use than Kubernetes.

Docker Drama

Docker Swarm is weird. For whatever reason, it has really useful features that aren’t available for Docker Compose and at the same time, there isn’t much love for it. Finding examples, tutorials, and services geared toward Docker Compose is easy, not so much for Docker Swarm which is a shame.

While I was figuring everything out I came across lots of stacks that could benefit from the features offered in Swarm mode, also a lot of stacks that don’t fully embrace the idea of microservices.

I’ll compare my BookStack stack to solidnerd’s, which isn’t that bad but is an easy example.

A bit of solidnerd’s Dockerfile

FROM php:7.2-apache-stretch
…
COPY php.ini /usr/local/etc/php/php.ini
COPY bookstack.conf /etc/apache2/sites-enabled/bookstack.conf
  1. Instead of using separate php and apache images it’s using a combined one so if there’s an update to apache you have to wait for it to be implemented in the php image if it is at all.
  2. It’s copying configuration files directly into the Docker image. If you want to change those configurations you have to recompile the entire image.

A bit of solidnerd’s docker-compose.yml

mysql:
    image: mysql:5.7.21
    environment:
    - MYSQL_ROOT_PASSWORD=secret
    - MYSQL_DATABASE=bookstack
    - MYSQL_USER=bookstack
    - MYSQL_PASSWORD=secret

The only thing here is the passwords which are stored as plain text in the docker-compose.yml, which of course isn’t great.

Now a bit of my docker-stack.yml

image: nginx:alpine
    volumes:
      - bookstack_public:/var/www/bookstack/public
    depends_on:
      - bookstack
    networks:
      - bookstack
    ports:
      - "80:80"
      - "443:443"
    configs:
      - source: bookstack_vhost
        target: /etc/nginx/conf.d/default.conf
    secrets:
      - source: YOURDOMAIN.com.crt
        target: /etc/nginx/certs/YOURDOMAIN.com.crt
      - source: YOURDOMAIN.com.key
        target: /etc/nginx/certs/YOURDOMAIN.com.key
      - source: dhparam.pem
        target: /etc/nginx/dhparam/dhparam.pem

The things that stand out here are the usage of configs and secrets, which are only supported by Docker Swarm.

Instead of copying configuration files directly into the base image you can copy them in at runtime. This has three benefits:

  1. You can change the configuration file without needing to recompile the image
  2. You can pull the latest image without needing to recompile
  3. If you have multiple stacks that use the same image you save space since you don’t need to store a separate version for each stack

Docker secrets have the benefit of not needing to store sensitive information directly in the configuration file.

Joining the Swarm

Docker Diagram
This is a simplified diagram of my Docker setup.

My goal was to make my server as easy to test and maintain as possible while keeping it lightweight, so I standardized what images I would use across all stacks to keep the complexity down and to save space. I took pre-existing stacks and swapped out Apache for NGINX and whatever database it was using for MariaDB. I cleaned up the main application images and used a mix of secrets, configs, and environment variables to make any configuration changes easier to maintain. I use the official base images for everything and for the couple of images that I do need to compile, I do that locally and push them to the server over ssh.