Skip to Content
⚠️ Dockflow is currently under development. Bugs may occur. Please report any issues on GitHub.

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.

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 (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/db

In .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

FieldRequiredDefaultDescription
roleNomanagerNode role: manager or worker
tagsYes-Environment tags (e.g., [production])
envNo-Server-specific environment variables
hostNoFrom connection stringOverride host (rarely needed)
userNodefaults.userOverride SSH user
portNodefaults.portOverride SSH port

Examples

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 production

This command will:

  • Open firewall ports (2377, 7946, 4789) on all nodes
  • Initialize Swarm on the manager
  • Join workers to the cluster

Deploy

dockflow deploy production

Swarm 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

GroupWhy
dockerAccess to the Docker socket, so deployments need no sudo at all
nginx groupGroup-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-token

Nothing 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 -c

Always 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:

VariableDescription
{{ 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.