Back to Flow

f deploy

docs/commands/deploy.md

0.1.311.7 KB
Original Source

f deploy

Deploy projects to hosts and cloud platforms.

Overview

The deploy command handles deployment to multiple platforms:

  • Linux hosts via SSH (with systemd + nginx)
  • Cloudflare Workers
  • Railway

Auto-detects the platform from your flow.toml configuration. If [flow].deploy_task is set, f deploy runs that task first. If no deployment config exists but a deploy task is defined, f deploy runs that task. Use f prod to deploy directly from [host], [cloudflare], [railway], or [web] (it skips [flow].deploy_task).

Quick Start

bash
# Auto-deploy based on flow.toml config
f deploy

# Production deploy (skips flow.deploy_task, uses deploy config or deploy-prod task)
f prod

# Run the project's release task (flow.release_task or fallback)
f deploy release

# Deploy to specific platform
f deploy host
f deploy cloudflare
f deploy railway

# Production deploy to specific platform
f prod host
f prod cloudflare

# Configure deployment defaults
f deploy config

# Deploy web site
f deploy web

Subcommands

CommandAliasDescription
hosthDeploy to Linux host via SSH
cloudflarecfDeploy to Cloudflare Workers
webDeploy the web site (Cloudflare)
setupInteractive deploy setup (Cloudflare)
railwayDeploy to Railway
configConfigure deployment defaults (Linux host)
releaseRun the project's release task
statusShow deployment status
logsView deployment logs
restartRestart the deployed service
stopStop the deployed service
shellSSH into the host
set-hostsetConfigure host for deployment
show-hostShow current host configuration
healthCheck if deployment is healthy

Web Deployment

Deploys the web site using Cloudflare and your project tasks. Flow will:

  • Ensure [web] exists in flow.toml (auto-fills path when possible).
  • Add the web.route (or web.domain/*) to your wrangler config.
  • Optionally create/update the Cloudflare DNS record for the domain/route.
  • Apply env vars from cloud if web.env_source = "cloud" is set.
  • Run deploy-web (or deploy as fallback).
bash
f deploy web

If the Cloudflare API token is missing, Flow will guide you to create one and store it in your env store as CLOUDFLARE_API_TOKEN.

If web.domain/web.route or web.path is missing, Flow will prompt for them and update flow.toml.

If you opt into DNS management, Flow will prompt for record type/target and create or update the Cloudflare DNS record (default: A to 192.0.2.1, proxied).

If cloud is unavailable, Flow can store envs locally when you choose web.env_source = "local".

Example flow.toml:

toml
[web]
path = "packages/web"
domain = "example.com"
env_source = "cloud"
env_apply = "always"
env_keys = ["PUBLIC_API_URL"]
env_vars = ["PUBLIC_API_URL"]

Host Deployment (Linux via SSH)

Deploy to any Linux server with SSH access. Flow handles:

  • File syncing via rsync
  • Systemd service creation
  • Nginx reverse proxy setup
  • SSL via Let's Encrypt

Configuration

Add to flow.toml:

toml
[flow]
deploy_task = "deploy-cli-release"

[host]
dest = "/opt/myapp"           # Remote destination path
run = "./server"              # Command to run the service
port = 3000                   # Port the service listens on
service = "myapp"             # Systemd service name (optional, defaults to folder name)
setup = "./scripts/setup.sh"  # Setup script to run after first sync (optional)
env_file = ".env.production"  # Path to .env file for secrets (optional)
env_source = "flow"           # Pull envs from Flow env store (optional)
env_keys = ["API_KEY"]        # Keys to fetch when env_source=flow/cloud (optional)
domain = "myapp.example.com"  # Public domain for nginx (optional)
ssl = true                    # Enable SSL via Let's Encrypt (optional)

Tip: f setup deploy can scaffold the [host] section and create a remote setup script.

Setup Host

First, configure your SSH connection:

bash
# Set host (stored globally at ~/.config/flow/deploy.json)
f deploy set-host user@host:port
f deploy set-host [email protected]:22
f deploy set-host [email protected]

# Interactive config (prefills from ~/.config/infra/config.json if present)
f deploy config

# Verify connection
f deploy shell

Deploy

bash
# Deploy to host
f deploy host

# Force re-run setup script
f deploy host --setup

# Build remotely instead of syncing local artifacts
f deploy host --remote-build

What Happens

  1. Sync files - rsync uploads project (excludes target/, .git/, node_modules/, .env, *.log)
  2. Copy env file - If env_file is specified, copies it to {dest}/.env (or, if env_source = "flow", fetches from Flow env store and writes {dest}/.env)
  3. Run setup - Executes setup script on first deploy or with --setup
  4. Create systemd service - Generates and enables /etc/systemd/system/{service}.service
  5. Configure nginx - If domain is set, creates reverse proxy config
  6. Setup SSL - If ssl = true, runs certbot for Let's Encrypt certificate
  7. Start service - Runs systemctl restart {service}

Manage Service

bash
# View logs
f deploy logs                 # Since last deploy (host only)
f deploy logs -f              # Follow in real-time (since last deploy)
f deploy logs --all           # Full history (ignore deploy marker)
f deploy logs --all -n 500    # Show last 500 lines without deploy filter

# Restart/stop
f deploy restart
f deploy stop

# Check status
f deploy status

# SSH into server
f deploy shell

# Health check
f deploy health
f deploy health --url https://myapp.example.com/health
f deploy health --status 204  # Expect specific status code

Note: For host deploys, Flow records the last successful deploy time in .flow/deploy-log.json and uses it to scope f deploy logs output. Use --all to ignore it.


Cloudflare Workers

Deploy to Cloudflare's edge network.

Configuration

Add to flow.toml:

toml
[cloudflare]
path = "worker"                    # Path to worker directory (optional, defaults to project root)
environment = "production"         # Wrangler environment name (optional)
env_file = ".env.cloudflare"       # Path to .env file for secrets (optional)
env_source = "cloud"              # Use cloud or local env store for secrets (optional)
env_keys = ["API_KEY", "SECRET"]   # Specific keys to fetch from env store (optional)
env_vars = ["PUBLIC_URL"]          # Keys to set as non-secret vars (optional)
deploy = "wrangler deploy"         # Custom deploy command (optional)
dev = "wrangler dev"               # Custom dev command (optional)
url = "https://my-worker.workers.dev"  # URL for health checks (optional)

Prerequisites

  • Wrangler CLI installed
  • wrangler.toml in your worker directory
  • Authenticated with wrangler login

Deploy

bash
# Deploy
f deploy cloudflare

# Deploy with secrets from env_file
f deploy cloudflare --secrets

# Run in dev mode
f deploy cloudflare --dev

Production domain (for f prod)

Use [prod] to set a production domain or route for Workers. f prod will add the route to your wrangler.json/jsonc before deploying.

toml
[prod]
domain = "anysynth.nikiv.com"   # Will be mapped to route "anysynth.nikiv.com/*"
# route = "anysynth.nikiv.com/*"  # Use this for explicit patterns

Interactive Setup

For first-time setup, use the interactive wizard:

bash
f deploy setup

This walks you through:

  1. Selecting worker directory (auto-discovers wrangler.toml)
  2. Choosing .env file for secrets
  3. Selecting Cloudflare environment (production, staging, etc.)
  4. Picking which secrets to push
  5. Updating flow.toml with your choices

If your flow.toml lists service keys (for example Stripe keys), the setup flow will offer to run the matching service onboarding before applying envs.

Secrets from cloud

If using cloud for secret management:

toml
[cloudflare]
env_source = "cloud"
env_keys = ["ANTHROPIC_API_KEY", "DATABASE_URL"]  # Fetched as secrets
env_vars = ["PUBLIC_API_URL"]                      # Fetched as non-secret vars
environment = "production"

Then deploy:

bash
f deploy cloudflare --secrets

If you want to use local Flow envs instead:

toml
[cloudflare]
env_source = "local"
env_keys = ["ANTHROPIC_API_KEY", "DATABASE_URL"]
env_vars = ["PUBLIC_API_URL"]

If you need to fill missing values first:

bash
f env guide

Railway

Deploy to Railway's platform.

Configuration

Add to flow.toml:

toml
[railway]
project = "my-project"         # Railway project ID
service = "api"                # Service name (optional)
environment = "production"     # Environment name (optional)
start = "npm start"            # Start command (optional)
env_file = ".env.railway"      # Path to .env file (optional)

Prerequisites

  • Railway CLI installed (npm install -g @railway/cli)
  • Authenticated with railway login

Deploy

bash
f deploy railway

What happens:

  1. Links to Railway project if specified
  2. Sets environment variables from env_file
  3. Deploys with railway up --detach

Health Checks

Check if your deployment is responding:

bash
# Use domain from [host] or url from [cloudflare] config
f deploy health

# Custom URL
f deploy health --url https://api.example.com/health

# Expect specific status code
f deploy health --status 204

Returns:

  • Healthy (HTTP 200 in 0.15s) on success
  • Unhealthy: expected HTTP 200, got 500 on wrong status
  • Unreachable: Connection refused on network error

Global Host Configuration

Host connection is stored globally at ~/.config/flow/deploy.json:

json
{
  "host": {
    "user": "deploy",
    "host": "myserver.com",
    "port": 22
  }
}

View/set:

bash
f deploy show-host
f deploy set-host [email protected]:2222

Examples

Full Host Setup

toml
# flow.toml
[host]
dest = "/opt/api"
run = "/opt/api/server"
port = 8080
service = "myapi"
setup = "cargo build --release && cp target/release/server /opt/api/"
env_file = ".env.production"
domain = "api.mycompany.com"
ssl = true
bash
# First time setup
f deploy set-host [email protected]

# Deploy
f deploy host

# Check it's working
f deploy health
f deploy logs -f

Full Cloudflare Setup

toml
# flow.toml
[cloudflare]
path = "packages/worker"
environment = "production"
env_source = "cloud"
env_keys = ["OPENAI_API_KEY", "WEBHOOK_SECRET"]
url = "https://my-worker.mycompany.workers.dev"
bash
# Store project secrets
f env project set -e production OPENAI_API_KEY=sk-...
f env project set -e production WEBHOOK_SECRET=whsec_...

# Deploy with secrets
f deploy cloudflare --secrets

# Verify
f deploy health

CI/CD Integration

yaml
# GitHub Actions
deploy:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Deploy
      run: |
        f deploy set-host ${{ secrets.DEPLOY_HOST }}
        f deploy host
        f deploy health

Troubleshooting

"No host configured"

Run f deploy set-host user@host:port first.

"No wrangler config found"

Ensure wrangler.toml exists in your worker directory, or run wrangler init.

SSH connection fails

Test with f deploy shell to debug. Check:

  • SSH key is in ~/.ssh/ and added to server
  • Port is correct (default: 22)
  • Server is reachable

Secrets not updating

For Cloudflare, use --secrets flag: f deploy cloudflare --secrets

Health check fails

Check URL is correct and service is running:

bash
f deploy logs -f  # Check for errors
curl -v https://your-domain.com  # Test manually