Dockflow

Quickstart

From a Linux server to a first zero-downtime deploy, a second deploy and a rollback.

This guide deploys a small web app to one server: an nginx container, served over HTTPS. You will:

  • install the CLI;
  • prepare the server with one command;
  • deploy the app, deploy a change, and roll it back.

It takes about ten minutes.

  1. Check the prerequisites.

    You need:

    • a Linux server (amd64 or arm64), new or already in use. Dockflow installs Docker on Debian and Ubuntu. On another distribution, install Docker Engine 27 or later first;
    • SSH access to it as root or as a user with passwordless sudo, or a root shell on it;
    • ports 80 and 443 of the server free and reachable from the internet;
    • a domain name whose DNS record points to the server. This guide uses hello.example.com.

    The full list is in server requirements. If an nginx on the server already holds ports 80 and 443, follow behind nginx for the server part.

  2. Install the CLI on your machine.

    curl -fsSL https://raw.githubusercontent.com/Shawiizz/dockflow/main/install.sh | sh
    dockflow version

    Homebrew, npm, Windows and other options are in installation.

  3. Prepare the server. Run server init from your machine or on the server itself. The result is the same.

    dockflow server init [email protected]

    Dockflow connects with your SSH key, from ssh-agent, ~/.ssh or -k <key>. It generates the deploy key on your machine, so the private key never reaches the server.

      connecting to 203.0.113.10:22 as root
      installing Docker from download.docker.com (debian bookworm)
      creating the user dockflow
      adding dockflow to the docker group
      installing the deploy key of dockflow
      connecting as dockflow with the deploy key
      installing the agent image dockflow/agent:1.0.0-alpha.6
    ✓ 203.0.113.10 is ready (edge: builtin, deploy user: dockflow)
    
    The connection string below holds the deploy key: store it as the <ENV>_CONNECTION secret
    …
    
    eyJ2IjoyLCJzZXJ2ZXJzIjpb…

    server init:

    • installs Docker when it is missing, from Docker's own repository;
    • creates the deploy user dockflow, in the docker group, with an SSH key restricted to what Dockflow uses. The private key is never written on the server;
    • reconnects as dockflow and installs the switch (dockflow-switch, a Caddy container that publishes ports 80 and 443 and obtains certificates) and the server-local registry (dockflow-registry, on 127.0.0.1:5959);
    • prints the connection string: the server, its host key and the deploy key, in one value.

    It leaves SSH's settings alone. Add --harden-ssh to turn passwords off and let root in by key only, once your account has a key.

    You can run it again at any time. On a prepared server, it changes nothing.

    The host key

    From your machine, Dockflow never trusts a server it cannot verify. If you have already connected to the server with ssh, its key is in your ~/.ssh/known_hosts and there is nothing to do.

    Otherwise, server init stops and shows the server's fingerprint. Compare it with the output of ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on the server, or with your provider's console. Then run it again with --host-key SHA256:….

    Create a directory for the project. Store the connection string in a local .env.dockflow file, kept out of git. In CI, it becomes the PRODUCTION_CONNECTION secret (see deploying from CI).

    .env.dockflow
    PRODUCTION_CONNECTION=eyJ2IjoyLCJzZXJ2ZXJzIjpb…
    echo .env.dockflow >> .gitignore

    The connection string contains the deploy key. Anyone who holds it controls Docker on the server, which means root. Never commit .env.dockflow. If it leaks, run server init again, which installs a new key, and remove the old line from /home/dockflow/.ssh/authorized_keys.

  4. Describe the environment.

    Write dockflow.yml at the root of the project. The server comes from the connection string, so the file does not mention it:

    dockflow.yml
    version: 1
    app: hello
    
    environments:
      production:
        vars:
          DOMAIN: hello.example.com
  5. Describe the app.

    Write compose.yml next to dockflow.yml. It is a standard Compose file. The x-dockflow key, which Compose ignores, adds a route.

    compose.yml
    services:
      web:
        image: nginx:1.27-alpine
        healthcheck:
          test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/"]
          interval: 5s
          timeout: 3s
          retries: 3
        x-dockflow:
          routes:
            - domains: ["${DOMAIN}"]
              port: 80
    • ${DOMAIN} takes its value from the environment's vars.
    • The route sends https://hello.example.com to port 80 of the container.
    • The healthcheck tells Dockflow when a new container is ready for traffic.

    dockflow init --domain hello.example.com writes these two files for you. With --ci github or --ci gitlab, it also writes a CI workflow (see init).

    Check the project without touching the server:

    dockflow validate production
    production: valid, 1 services
  6. Look at the plan.

    dockflow plan production
    Plan: 1 to create, 0 to update, 0 to remove
      · network.create hello-production_default: does not exist
      + service.create web [switch]: new service
      · routes.apply hello-production: routes changed

    The plan is computed from what the server actually runs. Nothing is changed yet.

  7. Deploy.

    dockflow deploy production

    On a terminal, deploy shows the same plan and asks for confirmation. -y skips the question, and CI never asks.

    Then the agent, a short-lived container on the server, starts web, waits for its healthcheck, points the route at it, and records release 1. The switch obtains a certificate for hello.example.com from Let's Encrypt on its own.

    curl https://hello.example.com/

    Since dockflow.yml has a single environment, you can leave out production in every command.

  8. Check the status.

    dockflow status production

    It shows the current release, its version label and the containers of each service. The version label is the short git commit, or the time of the deploy outside a git repository.

    dockflow ps lists the containers, and dockflow logs web shows their logs.

  9. Change something and deploy again.

    Change the image tag in compose.yml:

    compose.yml
    services:
      web:
        image: nginx:1.28-alpine
    dockflow deploy production
    Plan: 0 to create, 1 to update, 0 to remove
      ~ service.update web [switch]: image nginx@sha256:… → nginx@sha256:…

    The new container starts next to the old one. Once it is healthy, traffic moves to it. The old container gets 30 seconds to finish the requests in flight, then stops. No request fails during the switch.

    Deploying again without a change prints Nothing to change. and creates no release.

  10. Roll back.

    dockflow rollback production

    This restores the previous release, with the same zero-downtime switch. It uses the image still on the server, so nothing is rebuilt or downloaded. The rollback is itself a new release:

    dockflow releases production
    SEQ  VERSION  STATUS                     CREATED               BY
    3    a1b2c3d  active (rollback to 1)     2026-10-26T14:12:40Z  alice  ← current
    2    e4f5a6b  superseded                 2026-10-26T14:08:02Z  alice
    1    a1b2c3d  superseded                 2026-10-26T14:03:11Z  alice

    dockflow rollback --to 2 would restore release 2.

If a deploy fails, for example because the new container never becomes healthy, Dockflow switches back to the previous containers on its own and exits with code 8. See releases and rollbacks.

Next steps

Edit on GitHub

On this page