$ aoox

DocsInfrastructure

Docker Swarm

Run applications as a service with replicas across several machines, using the orchestrator already built into Docker — no Kubernetes.

What for
Several machines under one panel
Shape
Applications as a service + replicas
Who
Owner (init/leave/node), admin sees status

Swarm or remote servers?

Remote server (SSH)Swarm
ShapeThe panel controls another daemon over SSHOne cluster; Docker's scheduler places tasks
ApplicationA single container on the chosen serverN replicas that can be spread across several nodes
DomainProxy per server (or a host port)Traefik on the manager with the swarm provider
Good forSeparate environments, different regionsAdding capacity for the same application

You can use both at once: the aoox host becomes the swarm manager, while a remote server keeps serving other applications separately.

Flow

  1. 01initthe host becomes a manager, the aoox-swarm overlay is created
  2. 02joinother nodes join using a token from the panel
  3. 03servicean application is switched to deploy mode service + replicas
  4. 04rollingthe daemon replaces tasks gradually, rolling back on failure

Enabling swarm

  1. Settings → Docker Swarm → Init swarm (owner)

    Fill in Advertise address if the host has more than one IP — this is the address other nodes will use to reach the manager. Leave it blank for auto-detection.

    aoox turns the host daemon into a manager, creates an attachable overlay network called aoox-swarm, joins the proxy and managed databases to it, then enables Traefik's swarm provider.

  2. Join other nodes

    The Swarm card shows the full join command with its token — run it on the machine that will join (Docker already installed, ports 2377, 7946, and 4789/udp open between nodes):

    docker swarm join --token SWMTKN-1-… <manager-ip>:2377

    Worker and manager tokens differ; the token is only shown to the owner. A joined node appears in the node list a few seconds later.

  3. Make sure the registry is reachable from other nodes

    Nodes pull images themselves from the local registry. Swarm status warns if REGISTRY_PUBLIC_HOST is still localhost — other nodes won't be able to pull from it. Set it to a reachable hostname/IP (with TLS or insecure-registries) — see Registry.

Nodes over a VPN (WireGuard)

Nodes on different networks can be joined through a VPN tunnel so the swarm ports never face the internet. Set these when you init the swarm:

  • Advertise address = the manager's tunnel IP (e.g. 10.99.0.1). The control port 2377 then listens on that address only; Listen address overrides it. Without any address it stays on every interface.
  • Overlay MTU = the tunnel MTU, 1420 for WireGuard's default. Docker takes 50 off for the VXLAN header, so containers get 1370. Without it the overlay assumes a 1500 network (containers get 1450), and larger packets are dropped inside the tunnel.
  • Ports 2377/tcp, 7946 and 4789/udp only need to be reachable over the tunnel. On the worker, join with --advertise-addr <tunnel-ip> --listen-addr <tunnel-ip>:2377.

The MTU is fixed when the overlay network is created: to change it on an existing swarm, switch the applications back to container mode, leave the swarm and initialise it again.

Managing nodes

ActionEffect
Availability: activeThe node accepts new tasks.
Availability: pauseExisting tasks keep running; no new tasks are accepted.
Availability: drainTasks are moved to other nodes — use before maintenance.
Role: manager / workerPromote or demote a node. Keep the number of managers odd (1, 3, 5) for safe quorum.
LabelA key=value pair, e.g. zone=eu — used as a placement constraint.
Remove nodeRejected (409) if the node is still active; drain it first, then remove. On that node, run docker swarm leave.

Applications as a service

In the application's Settings, switch Deploy mode from container to service:

FieldNotes
ReplicasThe number of tasks to run. Stop scales to 0; Start restores it.
PlacementA specific node, or a free-form constraint (node.labels.zone==eu, node.role==worker, node.hostname, node.platform.os).
Rolling updateParallelism (how many tasks are replaced at once), the delay between batches, and the order (start-first / stop-first).
  • Changing the mode, replicas, placement, or limits runs as a config deployment — no build, and it doesn't block requests.
  • The daemon handles the rolling update itself; if new tasks never come up within the time limit, the old spec is restored and the application keeps serving.
  • An application with a mount is always placed on the host where its volume lives — Docker volumes don't move between nodes.
  • Redeploying with an identical spec still triggers a rolling update (ForceUpdate), so the Deploy button always has an effect.

What to know about tasks

AspectIn service mode
LogsAll tasks, from any node.
MetricsSummed from tasks running on the host — the Engine API can't read tasks on other nodes.
Job & terminal execLocal tasks only.
NotificationsA watcher alerts when a service stays below its replica count (tasks that never start), and another reports tasks that crash on other nodes within about 30 seconds.
DomainTraefik's swarm provider routes to the service, not to a container.

Leaving swarm

First switch every application back to Deploy mode: container — leaving swarm removes any running services. Then Settings → Docker Swarm → leave (owner). Other nodes need to run docker swarm leave themselves.
Restart Docker after leaving
Leaving a swarm triggers a Docker bug (moby/moby#45144, still open): the embedded DNS stops resolving container names on bridge networks for containers that are already running. The panel keeps working until its api container is recreated or restarted (for example by the next update), then it cannot reach postgres and goes down. Right after leaving, run this on the server; every aoox container starts again by itself:
sudo systemctl restart docker

Common pitfalls

SymptomCause & fix
Task pending: no suitable nodeThe constraint doesn't match any node's labels, or the app has a mount that pins it to the host. Loosen the placement.
On another node: image pull failed / x509REGISTRY_PUBLIC_HOST is still localhost, the registry has no TLS, or the node lacks an insecure-registries entry.
Service never reaches its replica countA health check is failing or resources are short; the rolling update auto-rolls-back after a timeout. Check the task logs.
Metrics empty for an app on another nodeExpected — metrics only come from local tasks.
Node missing after rebootThe node's daemon hasn't started, or a firewall is blocking a swarm port (2377/7946/4789).
Not available yet
Compose stacks as a swarm stack; Swarm secrets/configs; exec and metrics across nodes; automatic volume scheduling between nodes.

Next steps

Found a mistake? Edit this page on GitHub ↗