Skip to main content
Fly.io runs your containers on hardware in 30+ regions worldwide. Combined with Turso’s edge replicas, BunShip can serve requests with single-digit millisecond database latency from anywhere.

Prerequisites

Install flyctl:
# macOS
brew install flyctl

# Linux
curl -L https://fly.io/install.sh | sh

# Authenticate
fly auth login

Create the Fly App

1

Launch the app

Run fly launch from your project root. This creates a fly.toml configuration file.
fly launch --no-deploy
When prompted:
  • Choose an app name (e.g., bunship-api)
  • Select your primary region (e.g., iad for Ashburn, Virginia)
  • Decline the Postgres and Redis add-ons (we will configure them separately)
2

Configure fly.toml

Replace the generated fly.toml with this configuration:
app = "bunship-api"
primary_region = "iad"

[build]
  dockerfile = "docker/Dockerfile.api"

[env]
  NODE_ENV = "production"
  PORT = "3000"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 1

  [http_service.concurrency]
    type = "requests"
    hard_limit = 250
    soft_limit = 200

[[http_service.checks]]
  grace_period = "10s"
  interval = "30s"
  method = "GET"
  path = "/health"
  timeout = "5s"

[[vm]]
  size = "shared-cpu-1x"
  memory = "512mb"
  cpu_kind = "shared"
3

Set secrets

Store sensitive values as Fly secrets. These are encrypted and injected as environment variables at runtime.
fly secrets set \
  API_URL="https://bunship-api.fly.dev" \
  FRONTEND_URL="https://yourdomain.com" \
  TURSO_DATABASE_URL="libsql://your-db.turso.io" \
  TURSO_AUTH_TOKEN="your-turso-token" \
  JWT_SECRET="$(openssl rand -base64 32)" \
  JWT_REFRESH_SECRET="$(openssl rand -base64 32)" \
  REDIS_URL="redis://default:your-password@your-redis-host:6379" \
  STRIPE_SECRET_KEY="sk_live_xxx" \
  STRIPE_WEBHOOK_SECRET="whsec_xxx" \
  RESEND_API_KEY="re_xxx" \
  EMAIL_FROM="YourApp <[email protected]>"
4

Deploy

fly deploy
Fly builds the Docker image remotely using your Dockerfile.api, pushes it to its internal registry, and starts the machine. Watch the deploy output for the health check to pass.

Deploy the Worker

BunShip’s background worker (email sending, webhook delivery, billing sync) runs as a separate Fly app.
1

Create a worker app

fly apps create bunship-worker
2

Create fly.worker.toml

app = "bunship-worker"
primary_region = "iad"

[build]
  dockerfile = "docker/Dockerfile.api"

[env]
  NODE_ENV = "production"

[processes]
  worker = "bun run apps/api/src/worker.ts"

[[vm]]
  size = "shared-cpu-1x"
  memory = "512mb"
  cpu_kind = "shared"
The worker does not expose an HTTP service. It connects to Redis and processes jobs.
3

Set the same secrets

fly secrets set -a bunship-worker \
  TURSO_DATABASE_URL="libsql://your-db.turso.io" \
  TURSO_AUTH_TOKEN="your-turso-token" \
  REDIS_URL="redis://default:your-password@your-redis-host:6379" \
  JWT_SECRET="same-as-api" \
  STRIPE_SECRET_KEY="sk_live_xxx" \
  RESEND_API_KEY="re_xxx" \
  EMAIL_FROM="YourApp <[email protected]>"
4

Deploy the worker

fly deploy --config fly.worker.toml

Turso Integration

Turso and Fly.io work well together because both support edge deployments. Turso can replicate your database to the same regions where your Fly machines run.

Create Edge Replicas

# List available Turso regions
turso db locations

# Add replicas near your Fly regions
turso db replicate bunship-prod iad   # US East (Ashburn)
turso db replicate bunship-prod cdg   # Europe (Paris)
turso db replicate bunship-prod nrt   # Asia (Tokyo)
Turso automatically routes reads to the nearest replica. Writes go to the primary and replicate to all locations.

Multi-Region Fly Setup

To deploy your API in multiple regions:
# Scale to additional regions
fly scale count 2 --region iad
fly scale count 1 --region cdg
fly scale count 1 --region nrt
Each API instance reads from the nearest Turso replica, cutting response times for users outside your primary region.

Redis Setup with Upstash

Fly.io integrates with Upstash for managed Redis. Upstash provides a serverless Redis instance with a REST API and per-request pricing.
# Create an Upstash Redis database through Fly
fly redis create

# Follow the prompts to:
# - Name the database
# - Select a region (match your primary_region)
# - Choose a plan

# Fly automatically sets REDIS_URL on your app
BullMQ requires a persistent Redis connection. Make sure your Upstash plan supports persistent connections (not just the REST API). The Pro plan and above include this.

Custom Domains

# Add a certificate
fly certs create api.yourdomain.com

# Check certificate status
fly certs show api.yourdomain.com
Fly returns the IP addresses and CNAME target for your DNS configuration:
Type:  CNAME
Name:  api
Value: bunship-api.fly.dev

# Or for apex domains, use A/AAAA records:
Type:  A
Value: <fly-ipv4>
Type:  AAAA
Value: <fly-ipv6>
After DNS propagates, update API_URL:
fly secrets set API_URL="https://api.yourdomain.com"

Scaling and Regions

Vertical Scaling

Increase machine size for heavier workloads:
# Upgrade to a dedicated CPU
fly scale vm shared-cpu-2x --memory 1024

# Or use performance machines
fly scale vm performance-1x --memory 2048

Horizontal Scaling

Add more machines in the same or different regions:
# Scale to 3 machines in primary region
fly scale count 3 --region iad

# Add machines in Europe and Asia
fly scale count 1 --region cdg
fly scale count 1 --region nrt
Fly automatically load-balances across machines and routes users to the nearest region via Anycast.

Autoscaling

The fly.toml configuration above includes auto_stop_machines and auto_start_machines. Machines that receive no traffic stop automatically, and restart when a request arrives. This keeps costs low during off-peak hours while maintaining instant availability. Set min_machines_running = 1 to keep at least one machine warm for consistent response times.

Health Checks

The fly.toml includes an HTTP health check:
[[http_service.checks]]
  grace_period = "10s"
  interval = "30s"
  method = "GET"
  path = "/health"
  timeout = "5s"
Fly replaces machines that fail health checks. View machine status with:
fly status
fly machines list

Continuous Deployment

GitHub Actions

Add a deploy step to your CI pipeline using the official Fly GitHub Action:
# .github/workflows/deploy.yml
name: Deploy to Fly.io

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: superfly/flyctl-actions/setup-flyctl@master

      - name: Deploy API
        run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

      - name: Deploy Worker
        run: flyctl deploy --remote-only --config fly.worker.toml --app bunship-worker
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
Generate a deploy token:
fly tokens create deploy -x 999999h
Add the token as FLY_API_TOKEN in your GitHub repository secrets.

Monitoring

# Live logs
fly logs

# Logs for a specific machine
fly logs --instance <machine-id>

# Machine metrics
fly status
fly machines list
Fly provides built-in Grafana dashboards at fly.io/dashboard with CPU, memory, and network metrics per machine. For application-level monitoring, configure Sentry:
fly secrets set SENTRY_DSN="https://[email protected]/xxx"