docs/commands/deploy.md
Deploy projects to hosts and cloud platforms.
The deploy command handles deployment to multiple platforms:
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).
# 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
| Command | Alias | Description |
|---|---|---|
host | h | Deploy to Linux host via SSH |
cloudflare | cf | Deploy to Cloudflare Workers |
web | Deploy the web site (Cloudflare) | |
setup | Interactive deploy setup (Cloudflare) | |
railway | Deploy to Railway | |
config | Configure deployment defaults (Linux host) | |
release | Run the project's release task | |
status | Show deployment status | |
logs | View deployment logs | |
restart | Restart the deployed service | |
stop | Stop the deployed service | |
shell | SSH into the host | |
set-host | set | Configure host for deployment |
show-host | Show current host configuration | |
health | Check if deployment is healthy |
Deploys the web site using Cloudflare and your project tasks. Flow will:
[web] exists in flow.toml (auto-fills path when possible).web.route (or web.domain/*) to your wrangler config.web.env_source = "cloud" is set.deploy-web (or deploy as fallback).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:
[web]
path = "packages/web"
domain = "example.com"
env_source = "cloud"
env_apply = "always"
env_keys = ["PUBLIC_API_URL"]
env_vars = ["PUBLIC_API_URL"]
Deploy to any Linux server with SSH access. Flow handles:
Add to flow.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.
First, configure your SSH connection:
# 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 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
target/, .git/, node_modules/, .env, *.log)env_file is specified, copies it to {dest}/.env
(or, if env_source = "flow", fetches from Flow env store and writes {dest}/.env)--setup/etc/systemd/system/{service}.servicedomain is set, creates reverse proxy configssl = true, runs certbot for Let's Encrypt certificatesystemctl restart {service}# 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.
Deploy to Cloudflare's edge network.
Add to flow.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)
wrangler.toml in your worker directorywrangler login# Deploy
f deploy cloudflare
# Deploy with secrets from env_file
f deploy cloudflare --secrets
# Run in dev mode
f deploy cloudflare --dev
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.
[prod]
domain = "anysynth.nikiv.com" # Will be mapped to route "anysynth.nikiv.com/*"
# route = "anysynth.nikiv.com/*" # Use this for explicit patterns
For first-time setup, use the interactive wizard:
f deploy setup
This walks you through:
wrangler.toml)flow.toml with your choicesIf your flow.toml lists service keys (for example Stripe keys), the setup flow
will offer to run the matching service onboarding before applying envs.
If using cloud for secret management:
[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:
f deploy cloudflare --secrets
If you want to use local Flow envs instead:
[cloudflare]
env_source = "local"
env_keys = ["ANTHROPIC_API_KEY", "DATABASE_URL"]
env_vars = ["PUBLIC_API_URL"]
If you need to fill missing values first:
f env guide
Deploy to Railway's platform.
Add to flow.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)
npm install -g @railway/cli)railway loginf deploy railway
What happens:
env_filerailway up --detachCheck if your deployment is responding:
# 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 successUnhealthy: expected HTTP 200, got 500 on wrong statusUnreachable: Connection refused on network errorHost connection is stored globally at ~/.config/flow/deploy.json:
{
"host": {
"user": "deploy",
"host": "myserver.com",
"port": 22
}
}
View/set:
f deploy show-host
f deploy set-host [email protected]:2222
# 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
# First time setup
f deploy set-host [email protected]
# Deploy
f deploy host
# Check it's working
f deploy health
f deploy logs -f
# 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"
# 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
# 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
Run f deploy set-host user@host:port first.
Ensure wrangler.toml exists in your worker directory, or run wrangler init.
Test with f deploy shell to debug. Check:
~/.ssh/ and added to serverFor Cloudflare, use --secrets flag: f deploy cloudflare --secrets
Check URL is correct and service is running:
f deploy logs -f # Check for errors
curl -v https://your-domain.com # Test manually