flow-iron 0.5.0

Infrastructure-as-code CLI — deploy Docker Compose apps with Caddy reverse proxy and Cloudflare DNS
Documentation

iron

A CLI that deploys Docker Compose apps to bare-metal servers with automatic HTTPS, DNS, and zero-downtime updates.

Features

  • Single config file — define servers, apps, routing, and sidecars in fleet.toml
  • Docker Compose — generates compose files and deploys via SSH
  • Caddy reverse proxy — automatic HTTPS with generated per-app config fragments
  • Cloudflare DNS — creates and manages A records automatically
  • Zero-downtime deploys — rolling updates via docker-rollout, or stop-and-replace for stateful services
  • Server bootstrapping — provisions new machines with Ansible (Docker, firewall, fail2ban, hardening)
  • Auto-deploy — optional WUD (What's Up Docker) integration watches for new images and triggers deploys
  • Sidecar services — attach databases, caches, or any container alongside your app
  • Direct TCP — expose non-HTTP services (game servers, databases) with port mappings
  • Secrets management — env vars live in fleet.env.toml (gitignored), deployed as .env files

Installation

cargo install flow-iron

This installs two binaries — flow and iron — which are identical. Use whichever you prefer.

To build from source:

git clone https://github.com/flow-industries/iron.git
cd iron
cargo install --path .

Prerequisites

  • SSH agent with key for target servers
  • Cloudflare API token and GHCR token — flow login will guide you through setup
  • Python/pip (only for flow server add — Ansible is auto-installed if missing)

Quick Start

# Initialize a new fleet.toml (prompts for tokens)
flow init

# Or add/update tokens separately
flow login        # set up Cloudflare + GitHub tokens
flow login cf     # Cloudflare only
flow login gh     # GitHub only

# Add a server (creates DNS record, bootstraps via Ansible)
flow server add srv-1 --ip 164.90.130.5

# Add an app with routing
flow app add site --image ghcr.io/org/site:latest --server srv-1 --port 3000 \
    --domain example.com --health-path /health

# Add a worker (no routing needed)
flow app add worker --image ghcr.io/org/worker:latest --server srv-1

# Add a game server with direct TCP
flow app add game --image ghcr.io/org/game:latest --server srv-1 \
    --deploy-strategy recreate --port-map 9999:9999/tcp

# Add sidecar services
flow app add-service site postgres --image postgres:17 \
    --volume pgdata:/var/lib/postgresql/data --healthcheck "pg_isready -U app"

# Deploy
flow deploy         # deploy all apps
flow deploy site    # deploy a single app

Commands

flow init                        # create fleet.toml
flow deploy [app]                # deploy all or one app
flow status [--server srv-1]     # fleet status and container info
flow check [--server srv-1]      # verify fleet.toml matches servers
flow stop <app> [--server srv-1] # stop an app
flow restart <app>               # restart containers
flow remove <app> [--yes]        # tear down app, remove from fleet.toml
flow logs <app> [-f]             # tail logs

flow app add <app> ...           # add app to fleet.toml
flow app add-service <app> ...   # add sidecar service
flow app remove-service <app> .. # remove sidecar service

flow server add <name> --ip ..   # add and bootstrap a server
flow server remove <name>        # remove a server
flow server check [name]         # check server health

flow login                       # set up all tokens (Cloudflare + GitHub)
flow login cf                    # set/update Cloudflare API token
flow login gh                    # set/update GitHub (GHCR) token

How It Works

flow deploy <app>
  1. Parse fleet.toml + fleet.env.toml
  2. Generate docker-compose.yml and .env
  3. SSH to target server
  4. Upload files to /opt/flow/<app>/
  5. Pull images, rolling deploy (or recreate)
  6. Generate Caddy config fragment, reload Caddy
  7. Ensure Cloudflare DNS A record

Configuration

fleet.toml is the single source of truth for your infrastructure. fleet.env.toml (gitignored) holds all environment variables and secrets.

[fleet]
domain = "example.com"
cloudflare_zone_id = "your-zone-id"

[servers.srv-1]
ip = "164.90.130.5"

[apps.site]
image = "ghcr.io/org/site:latest"
servers = ["srv-1"]
port = 3000

[apps.site.routing]
domains = ["example.com", "www.example.com"]
health_path = "/health"

[[apps.site.services]]
name = "postgres"
image = "postgres:17"
volumes = ["pgdata:/var/lib/postgresql/data"]
healthcheck = "pg_isready -U app"

License

MIT