So you’re a web application developer and you want to get started with Docker Swarm? You’re in the right place. When I was first learning how to incorporate Docker and Docker Swarm into my existing toolset, there were plenty of examples but none that matched my intended stack of software.
All it came down to was knowing the right base images to use and which software would and would not work with those images. Nevertheless, if I can help one person to skip that process, this article will be well worth it 🙂
Note: Since this is meant to be an introduction to Docker Swarm, the examples below assume you’re on a single-node Swarm cluster. Though most of the following examples will work in a distributed environment, the complexities of a distributed database in Swarm (whether something as simple as SQLite or not) is beyond the scope of this article.
For me, the main reasons you’d want to use Docker and Docker Swarm for your web application stack are:
Ok, before showing the example web application stack in Docker Swarm, I want to define a few things in case some of this sounds confusing.
Now that you have an understanding of some of the basic components of Docker Swarm, the rest of this article will be focused on the configuration files you’ll need to get a web application stack running in Swarm. The components I’m using are as follows:
This is an example from a Vue.js project of mine but the same Dockerfile should work with other JavaScript frameworks which use npm for build and dependency management. You’ll see by the final line of this Dockerfile that the frontend is hosted using nginx. This file goes in the root of your project (i.e. where package.json lives).
### Build stage ###
# Using a separate build stage so that only the built frontend will go in the final image
FROM node:8-slim as build-stage
WORKDIR /app
# Copy and install dependencies
COPY package*.json ./
RUN npm install
# Copy our app into the image and build it. Doing this as a separate step creates a separate
# "layer" so if the application changes but the requirements.txt does not, the next image build
# process will start here instead of above.
COPY . .
RUN npm run build
### Production stage ###
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
To build the frontend Docker image, just execute the following in the root of your project:
docker build -t frontend .
For completeness sake, here is a sample nginx configuration file you could use:
server {
listen 80;
server_name makeshiftinsights.com;
access_log /dev/stdout;
error_log /dev/stderr;
root /usr/share/nginx/html;
index index.html;
# Proxy requests to "/auth" and "/api" to backend
location ~ ^/(auth|api){
proxy_pass http://backend;
proxy_redirect off;
}
# Everything else goes to index.html
location / {
try_files $uri /index.html;
}
}
Due to its popularity, the following assumes you’re using the Python package Flask for web service development. There are plenty of examples out there if you’re using something different. If you’re starting from scratch and familiar with Flask, I’d recommend using FastAPI as a more performant alternative. As a bonus, you can find a predefined Docker base image for FastAPI here.
# Base image
FROM python:3.6
# Install python and uwsgi
RUN apt-get update
RUN apt-get install -y nginx
RUN pip3 install uwsgi
WORKDIR /app
# Copy and install requirements
COPY service/requirements.txt .
RUN pip3 install -r requirements.txt
# Copy our app into the image. Doing this as a separate step creates a separate "layer"
# so if the application changes but the requirements.txt does not, the next image build
# process will start here instead of above.
COPY . .
# Setup and start nginx
COPY ./nginx.conf /etc/nginx/sites-enabled/default
CMD service nginx start && uwsgi -s /tmp/uwsgi.sock --chmod-socket=666 --manage-script-name --mount /=api:app
This Dockerfile expects your Flask entrypoint file to be located at api.py
and the Flask instance within that file is named app
.
To build the backend Docker image, just execute the following in the root of your project:
docker build -t backend .
The stack itself is defined in a docker-compose.yml file like the following.
version: '3'
services:
frontend:
image: frontend:latest
ports:
- "80:80" # Exposes port 80 from the container as port 80 on the host
backend:
image: backend:latest
volumes:
- database-volume:/app/data
volumes:
database-volume:
I tend to put the compose file in its own repository along with any other project level files put it can also live wherever you wish as long as you can access it when running the deploy command (see below).
A database server isn’t needed in many cases so we use a volume (called database-volume above) to store a SQLite database file.
To start all this up, assuming you have Docker installed, all you have to do is execute the following:
docker swarm init
docker stack deploy -c docker-compose.yml my-stack-name
If you decide you do need a database server, there are plenty of options. There is an official Docker image for MySQL and you would need a mostly empty project/repository with the following Dockerfile:
# Base image
FROM mysql:5
# Copy initialization files
COPY init/ /docker-entrypoint-initdb.d/
Any SQL files located under /docker-entrypoint-initdb.d/ will be executed once the container starts up for the first time. You’d also have to add this as another service to the compose file above like this:
sm-database:
image: sm-database
# If we want to use database tools
ports:
- "3306:3306"
See the docs for more information on how to use this image.