Improving the Process
At this point, we have a small handful of containers running, and are likely looking at adding more to the list. It's only reasonable to consider the maintainability of the resulting system, and the fact of the matter is, the prospects of keeping going with individual containers on the swarm are poor. A better solution is needed. Docker of course offers Compose, but that's more of a development tool. It doesn't persist across restarts of the runtime, doesn't scale to multiple worker hosts, if that's the direction we are interested in, On the other hand, it does manage groups of Docker images well, allows expressing dependencies between containers, and can manage both stock and custom images. Fortunately, Docker provides the Docker Stack system to leverage Compose across a swarm, allowing us the best of both worlds.
First, we'll install docker compose.
$ paru -S docker-compose
We need to set up the compose file. Unfortunately, Docker Stack does not support include yet, so we have to maintain a giant docker-compose file. Create docker-compose.yml with the following contents. Note that these compose files will not work in Docker Compose - the labels are defined for stack deployments.
networks:
homelab:
driver: overlay
ipam:
config:
- subnet: 10.64.0.0/16
internal: true
homelab-bridge:
driver: overlay
ipam:
config:
- subnet: 10.96.0.0/16
secrets:
percona-root:
file: /home/andreas/secrets/percona-root
ghost:
file: /home/andreas/secrets/ghost
configs:
ghost-config:
file: /home/andreas/configs/config.production.json
traefik-config:
file: /home/andreas/configs/traefik.toml
services:
traefik:
configs:
- source: traefik-config
target: /etc/traefik/traefik.toml
deploy:
labels:
traefik.enable: "true"
traefik.http.routers.traefik-rtr.entrypoints: "websecure"
traefik.http.routers.traefik-rtr.middlewares: "traefik-allowlist@file"
traefik.http.routers.traefik-rtr.rule: "Host(`traefik.turriff.net`)"
traefik.http.routers.traefik-rtr.service: "api@internal"
traefik.http.services.dummy-svc.loadbalancer.server.port: "9999"
replicas: 1
healthcheck:
test: ["CMD","traefik","healthcheck"]
interval: 30s
timeout: 10s
image: "traefik:3.0"
logging:
driver: journald
networks:
- homelab
- homelab-bridge
ports:
- published: 80
target: 10080
protocol: tcp
mode: host
- published: 443
target: 10443
protocol: tcp
mode: host
restart: on-failure
volumes:
- "/srv/data/docker/traefik/rules:/rules:ro"
- "/srv/data/docker/traefik/acme:/acme:rw"
- "/srv/data/docker/traefik/logs:/logs:rw"
- "/run/docker.sock:/run/docker/sock:ro"
percona:
deploy:
replicas: 1
environment:
MYSQL_ROOT_PASSWORD_FILE: "/run/secrets/percona-root"
PERCONA_TELEMETRY_DISABLE: 1
hostname: "percona"
image: "percona:ps-8"
logging:
driver: journald
networks:
- homelab
restart: on-failure
secrets:
- percona-root
- ghost
volumes:
- "/srv/data/docker/percona/data:/var/lib/mysql:rw"
ghost:
configs:
- source: ghost-config
target: /var/lib/ghost/config.production.json
depends_on:
- percona
deploy:
labels:
traefik.enable: "true"
traefik.http.routers.ghost-rtr.entrypoints: "websecure"
traefik.http.routers.ghost-rtr.rule: "Host(`ghost.turriff.net`)"
traefik.http.routers.ghost-rtr.service: "ghost"
traefik.http.services.ghost.loadbalancer.server.port: "2368"
replicas: 1
image: "ghost:5"
logging:
driver: journald
networks:
- homelab
restart: on-failure
volumes:
- "/srv/data/docker/ghost/data:/var/lib/ghost/content:rw"
This does pretty much the same our assorted docker service create and docker service update commands did earlier. With the file set up, let's clean up the individually managed services and get the stack running
$ docker service rm ghost
$ docker service rm percona
$ docker service rm traefik
$ docker config rm ghost
$ docker config rm traefik
$ docker secret rm ghost
$ docker secret rm percona-root
$ docker stack deploy -c docker-compose.yml --prune homelab
The stack will check for updates every time it's brought online. To account for this, we will avoid using "latest" tags fpr our images and explicitly specify at least major versions. Even with the giant compose file, this should make managing the home lab a lot easier.
Updating images is easily accomplished with
$ docker compose -f docker-compose.yml pull
$ docker stack deploy -c docker-compose.yml --prune homelab