Servers Configuration
The servers.yml file defines your server topology (roles, tags) and environment variables. Connection credentials are provided separately via connection strings generated by dockflow setup.
Architecture
Dockflow uses Docker Swarm for orchestration. Each environment has one manager that receives deployments and optional workers that run distributed containers.
Basic Structure
# .dockflow/servers.yml
servers:
main_server:
role: manager
tags: [production]
defaults:
user: dockflow
port: 22
env:
all:
LOG_LEVEL: "info"
production:
LOG_LEVEL: "warn"This defines a single server named main_server with the manager role. Connection details (host, SSH key) come from secrets — not from this file.
Secrets
Dockflow needs SSH credentials to connect to your servers. The recommended approach is connection strings, generated automatically by dockflow setup.
Connection String (Recommended)
A connection string is a base64-encoded JSON containing all credentials for one server:
{
"host": "192.168.1.10",
"port": 22,
"user": "dockflow",
"privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
"password": "optional-sudo-password"
}Store it as [ENV]_[SERVERNAME]_CONNECTION:
.env.dockflow (local CLI)
# .env.dockflow (at project root — do not commit)
PRODUCTION_MAIN_SERVER_CONNECTION=eyJob3N0Ijoi...Do not share connection strings — they contain SSH private keys.
Server names use underscores in secret names. A server named worker_1 uses PRODUCTION_WORKER_1_CONNECTION.
Variable Overrides
Override any environment variable via secrets:
# Global override (all servers in production)
PRODUCTION_DATABASE_URL=postgres://secret:5432/db
# Server-specific override
PRODUCTION_MAIN_SERVER_DATABASE_URL=postgres://primary:5432/dbIn .env.dockflow, a multi-line value such as a private key goes between quotes, and runs up to
the line that ends with the closing quote:
# .env.dockflow
PRODUCTION_MAIN_SERVER_DEPLOY_KEY="-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
...
-----END OPENSSH PRIVATE KEY-----
"Put the closing quote on its own line to keep the trailing newline, which OpenSSH expects. Escape
sequences such as \n are not expanded.
Server Fields
| Field | Required | Default | Description |
|---|---|---|---|
role | No | manager | Node role: manager or worker |
tags | Yes | - | Environment tags (e.g., [production]) |
env | No | - | Server-specific environment variables |
host | No | From connection string | Override host (rarely needed) |
user | No | defaults.user | Override SSH user |
port | No | defaults.port | Override SSH port |
Examples
Single-Node
For simple deployments, a single manager is sufficient:
servers:
production_server:
role: manager
tags: [production]Cluster Setup
Before your first multi-node deployment, initialize the Swarm cluster:
Configure secrets
Add connection strings for all nodes (manager + workers) in .env.dockflow or your CI/CD platform.
Initialize Swarm
dockflow setup swarm productionThis command will:
- Open firewall ports (2377, 7946, 4789) on all nodes
- Initialize Swarm on the manager
- Join workers to the cluster
Deploy
dockflow deploy productionSwarm automatically distributes containers to workers based on resource availability and placement constraints.
You only need to run setup swarm once per environment. After that, just use dockflow deploy.
Deploy User Permissions
dockflow setup creates the deploy user with useradd and never adds it to the sudo
group. It is a machine account whose SSH key lives in your CI secrets, so it is granted only
what a deployment actually needs.
Group membership
| Group | Why |
|---|---|
docker | Access to the Docker socket, so deployments need no sudo at all |
| nginx group | Group-write on /etc/nginx/sites-enabled, so the nginx plugin writes vhosts without sudo |
The nginx group is taken from the user directive in nginx -T, falling back to nginx and
then www-data. Setup also runs chgrp -R and chmod -R g+rwX on sites-enabled.
Sudo rules
Setup writes /etc/sudoers.d/<username> (mode 0440) with exactly three entries — binary
paths are resolved with which at setup time:
dockflow ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t, /usr/sbin/nginx -s reload
dockflow ALL=(ALL) NOPASSWD: /usr/local/bin/k3s ctr -n k8s.io images *
dockflow ALL=(ALL) NOPASSWD: /bin/cat /var/lib/rancher/k3s/server/node-tokenNothing else is granted. In particular there is no rule for systemctl — a hook running
sudo systemctl daemon-reload will fail on a normally provisioned server.
Put your own rules in your own file. Setup rewrites /etc/sudoers.d/<username> every time
it runs, so anything you add there is lost on the next dockflow setup. Use a separate file:
echo 'dockflow ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload' | sudo tee /etc/sudoers.d/dockflow-myapp > /dev/null
sudo chmod 440 /etc/sudoers.d/dockflow-myapp
sudo visudo -cAlways finish with visudo -c: a syntax error in a sudoers file can lock you out of sudo
entirely. Keep a second session open while you edit.
These rules are defence in depth, not a security boundary. Membership of the docker group is
equivalent to root — anyone who can reach the Docker socket can start a privileged container
and take the host. The restricted sudo still limits accidents and documents intent, and it
becomes a real boundary with orchestrator: k3s or rootless Podman, where that shortcut does
not exist. Protect the CI key accordingly.
Environment Variables
Define variables that are inherited based on tags:
env:
# Applied to ALL environments
all:
APP_NAME: "{{ project_name }}"
LOG_LEVEL: "info"
TZ: "UTC"
# Override for production
production:
LOG_LEVEL: "warn"
DATABASE_URL: "postgres://prod-db:5432/app"
# Override for staging
staging:
LOG_LEVEL: "debug"Variable Priority
Variables are resolved from lowest to highest priority. A higher-priority source always overrides a lower one:
Templating
Values in servers.yml support Nunjucks templating:
env:
all:
APP_NAME: "{{ project_name }}"
STACK_NAME: "{{ project_name }}-{{ env }}"Available variables:
| Variable | Description |
|---|---|
{{ project_name }} | From config.yml |
{{ env }} | Current environment being deployed |
{{ version }} | Deployment version |
{{ current.name }} | Current server name |
{{ current.host }} | Current server host |
{{ current.role }} | Current server role (manager/worker) |
Next: Docker Compose — define your application services, networks, and volumes.