In my previous post Add HTTPS support for your app backend API in 5 mins with traefik as a reverse proxy, I demonstrated how to deploy a service with docker-compose and Traefik. But docker-compose is really meant to be use in development, not production, although it still works decently for small apps.
So in this post, I will show you how to make some modifications to the docker-compose file so you can deploy your services with docker swarm mode, which supports multiple instances of docker containers running on different machines and scales better for larger apps.
I recommend read my previous post and get you app running with docker-compose before reading this post.
Prerequisites
- everything used in the previous post
- make sure you have a folder name
letsencrypt - if you haven't enabled swarm mode, run
docker swarm init
Update docker-compose
Add a network at the bottom of the file. This is necessary. For some reason, the default network doesn't seem to work in swarm mode.
+ networks:
+ overlay:Under command of the service traefik, add
traefik:
image: "traefik:v2.2"
container_name: "traefik"
command:
#- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
+ - "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
# - "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.myresolver.acme.email=johndoe@gmail.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"Under the networks, add
+ networks:
+ - overlay:This will put traefik on the overlay network, allowing traefik to communicate with whatever services you're running, which we will put on overlay as well.
Finally, add deploy under traefik. Note that traefik needs to be put on a manager node on the swarm, thus the constraint node.role == manager. And we only want 1 traefik instance on the swarm, so we're using global mode. Learn more here
+ deploy:
+ mode: global
+ placement:
+ constraints:
+ - node.role == manager
+ update_config:
+ parallelism: 1
+ delay: 10s
+ restart_policy:
+ condition: on-failureThat's it for the traefik service! Now let's handle your api service. Remember to replace example.com with your domain name. In this part we
- put your api service on
overlay - add the
deploysection to specify configs for deployment
api:
+ networks:
+ - overlay
+ deploy:
+ replicas: 1
+ update_config:
+ parallelism: 2
+ delay: 10s
+ restart_policy:
+ condition: on-failure
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.api.rule=Host(`example.com`)"
+ - "traefik.http.routers.api.entrypoints=websecure"
+ - "traefik.http.routers.api.tls.certresolver=myresolver"
+ - "traefik.http.services.api.loadbalancer.server.port=80"
+ - "traefik.docker.network=api_overlay"You've probably noticed the labels are very similar to the old laybels we add to the container in the previous post. The reason is that when using swarm mode, traefik loads from service labels instead of container labels, so the labels we set for the container will NOT be read by traefik.
Also notice that we added - "traefik.http.services.api.loadbalancer.server.port=80" This tells traefik which port it should use to communicate with api.(the port your docker container exposes). We didn't need to specify the port when using docker-compose because traefik automatically detects the port. However, in swarm, you have to explicitly tell traefik which port to use.
- "traefik.docker.network=biased-api_overlay" on the other hand, tells traefik which network to use to communicate with the service. Make sure to prefix the network name, in this case, overlay, with the name of your stack.(You can specify your stack name when you run the docker stack deploy command later)
Final Result
version: "3.3"
services:
traefik:
image: "traefik:v2.2"
container_name: "traefik"
command:
#- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
+ - "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
# - "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.myresolver.acme.email=johndoe@gmail.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
+ networks:
+ - overlay
+ deploy:
+ mode: global
+ placement:
+ constraints:
+ - node.role == manager
+ update_config:
+ parallelism: 1
+ delay: 10s
+ restart_policy:
+ condition: on-failure
api:
image: "jamesku/api"
container_name: "api"
ports:
- "5001:80"
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=myresolver"
+ networks:
+ - overlay
+ deploy:
+ replicas: 1
+ update_config:
+ parallelism: 2
+ delay: 10s
+ restart_policy:
+ condition: on-failure
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.api.rule=Host(`example.com`)"
+ - "traefik.http.routers.api.entrypoints=websecure"
+ - "traefik.http.routers.api.tls.certresolver=myresolver"
+ - "traefik.http.services.api.loadbalancer.server.port=80"
+ - "traefik.docker.network=api_overlay"
+networks:
+ overlay:Deployment
Now that everything is ready, we can now run the deploy command.
- deploy
docker stack deploy -c docker-compose.yml <stack name> - check the status of the stack
docker stack ps <stack name> - list services
docker service ls
If you want to stop your service, run docker stack rm <stack name>
That's it. You've successfully deployed your app with docker swarm. You can easily scale it by adding more nodes to your swarm and changing the replicas parameter.