DEPLOY May 5, 2026 9 min read

Deploy an AI Bot to Fly.io: Complete Step-by-Step Guide

TL;DR Fly.io deploys containerized AI bots globally with a generous free tier (3 shared VMs, 160 GB outbound). Full deployment takes under 15 minutes once your Dockerfile is ready. Secrets…

by Bugi 9 min
TL;DR

  • Fly.io deploys containerized AI bots globally with a generous free tier (3 shared VMs, 160 GB outbound).
  • Full deployment takes under 15 minutes once your Dockerfile is ready.
  • Secrets are injected as environment variables — never baked into images.

Overview

This guide walks through deploying a Python-based AI bot (e.g., a Claude API wrapper, Discord bot, or Telegram bot) to Fly.io. Fly runs Docker containers on lightweight VMs (Machines) across 30+ regions. You get automatic TLS, built-in secrets management, and persistent volumes for SQLite or local state.

Total time: ~15 minutes. Cost: free tier covers most single-bot deployments. If you exceed the free allowance, expect $1.94/mo for a shared-1x-256mb machine running 24/7.

What You’ll Need

  • A working AI bot with a Dockerfile (or at minimum a requirements.txt)
  • A Fly.io account (credit card required for verification, not charged on free tier)
  • flyctl CLI installed locally
  • Your AI provider API key (OpenAI, Anthropic, etc.)
  • Optional: a custom domain for webhook endpoints
Tip
If your bot uses webhooks (Telegram, Slack), you’ll need a public URL. Fly provides one automatically at your-app.fly.dev.

Step 1: Install flyctl and Authenticate

1
Install the CLI
One command for macOS/Linux. Windows uses PowerShell.
2
Authenticate
Opens browser for OAuth login.
# macOS / Linux
curl -L https://fly.io/install.sh | sh

# Verify installation
flyctl version
# Login (opens browser)
flyctl auth login

After authentication, confirm your account:

flyctl auth whoami

If you’re behind a corporate proxy or headless server, use flyctl auth token to authenticate via a manually generated token instead.

Step 2: Prepare Your Project

Your project needs three files at minimum: application code, a Dockerfile, and a Fly config.

Dockerfile — keep it minimal:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "bot.py"]

Initialize the Fly app:

cd your-bot-directory
flyctl launch

This interactive prompt creates fly.toml. Choose these settings:

  • App name: your-bot-name (or accept the generated one)
  • Region: pick the closest to your users or API provider
  • Deploy now: No (configure secrets first)

The generated fly.toml looks like:

app = "your-bot-name"
primary_region = "iad"

[build]

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0
Warning
If your bot is a long-running process (not an HTTP server), remove the [http_service] block entirely and add [processes] instead. Otherwise Fly will kill it for failing health checks.

For non-HTTP bots (Discord bots, Telegram polling, background workers), replace [http_service] with:

[processes]
  app = "python bot.py"

[[vm]]
  size = "shared-cpu-1x"
  memory = "256mb"

Step 3: Configure Secrets

Never put API keys in your Dockerfile or fly.toml. Fly injects secrets as environment variables at runtime.

# Set your AI provider key
flyctl secrets set ANTHROPIC_API_KEY=sk-ant-xxxxx

# Set other bot credentials
flyctl secrets set TELEGRAM_BOT_TOKEN=123456:ABC-xyz
flyctl secrets set DATABASE_URL=sqlite:///data/bot.db

Verify secrets are registered (values stay hidden):

flyctl secrets list

~/your-bot

$ flyctl secrets list
NAME                 DIGEST          CREATED AT
ANTHROPIC_API_KEY    a1b2c3d4        2m ago
TELEGRAM_BOT_TOKEN   e5f6g7h8        1m ago
DATABASE_URL         i9j0k1l2        30s ago

Your application code reads these normally via os.environ["ANTHROPIC_API_KEY"] — no code changes needed.

Step 4: Add Persistent Storage

If your bot writes to disk (SQLite, logs, file cache), attach a volume. Without one, data vanishes on every deploy.

# Create a 1GB volume in your app's region
flyctl volumes create bot_data --region iad --size 1

Mount it in fly.toml:

[mounts]
  source = "bot_data"
  destination = "/data"

Update your bot to use /data/ as its storage path. For SQLite:

import os

DB_PATH = os.environ.get("DB_PATH", "/data/bot.db")
Note
Volumes are pinned to a single region and a single machine. If you scale to multiple machines, each gets its own volume — not a shared filesystem.

Step 5: Deploy

flyctl deploy

This builds your Docker image remotely (Fly’s builders), pushes it, and starts the machine. First deploy takes 1-3 minutes depending on image size.

~/your-bot

$ flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
--> Building image done
==> Pushing image to fly
 Machine started successfully
--> Checking DNS propagation
 App deployed: your-bot-name.fly.dev

Step 6: Verify Deployment

Check your bot is running:

# Machine status
flyctl status

# Live logs
flyctl logs

# SSH into the running machine
flyctl ssh console

For HTTP bots, hit the health endpoint:

curl https://your-bot-name.fly.dev/health

For non-HTTP bots, confirm via logs that your bot connected successfully:

flyctl logs --no-tail | head -20

If your bot uses webhooks, register the Fly URL with your platform:

# Telegram example
curl "https://api.telegram.org/bot${TOKEN}/setWebhook?url=https://your-bot-name.fly.dev/webhook"

Troubleshooting

Bot exits immediately with no logs

Your process likely isn’t staying alive. For polling bots, ensure you have a blocking call (e.g., bot.polling() or asyncio.run(main())). Check with flyctl logs — if empty, the container crashed before stdout flushed. Add flush=True to print statements or configure Python’s -u flag in your CMD.

Health check failures (HTTP bots)

Fly expects a response on internal_port within 10 seconds. If your bot takes longer to start, increase the grace period:

[http_service]
  internal_port = 8080

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

Out of memory on shared-cpu-1x

The 256 MB default is tight for AI workloads that load large prompt templates. Scale up:

flyctl scale memory 512

Or switch VM size in fly.toml:

[[vm]]
  size = "shared-cpu-1x"
  memory = "512mb"
Danger
Never log or print your API keys. If they appear in flyctl logs, rotate them immediately — Fly logs are accessible to all org members.

Production Hardening

Once your bot is running, lock it down:

Auto-restart on crash:

Fly Machines restart automatically by default. Confirm with:

flyctl machine list

The restart policy should show on-failure.

Scheduled scaling (save costs):

If your bot only needs to run during business hours:

[http_service]
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

Non-HTTP bots stay running unless you explicitly stop them with flyctl machine stop.

Monitoring:

# CPU and memory metrics
flyctl monitor

# Set up alerting (sends to your email)
flyctl checks list
Takeaway

Deploy, attach a volume for persistence, inject secrets as env vars, and verify via logs. That’s the entire workflow.

FAQ

How much does it cost to run an AI bot on Fly.io 24/7?
The free tier includes 3 shared-cpu-1x VMs with 256 MB RAM. A single always-on bot fits within this. If you exceed the free allowance, a shared-cpu-1x with 256 MB costs approximately $1.94/month. Adding a 1 GB volume adds $0.15/month.
Can I use SQLite on Fly.io?
Yes, but you must attach a persistent volume. Without a volume, the filesystem resets on every deploy. Mount a volume to /data and point your SQLite path there. Note that SQLite on Fly works best with a single machine — concurrent writes from multiple machines will corrupt the database.
How do I update my bot after the initial deploy?
Run `flyctl deploy` again. Fly builds a new image, performs a rolling update, and routes traffic to the new machine once healthy. For zero-downtime deploys of HTTP bots, Fly handles this automatically. Non-HTTP bots experience a brief restart.
My bot needs to stay running but Fly keeps stopping it. How do I fix this?
Remove the [http_service] block from fly.toml. That block enables auto-stop behavior for HTTP services. For long-running processes, use [processes] to define your command. The machine will stay alive as long as the process runs.
Can I deploy multiple bots in one Fly app?
Yes, using Fly’s multi-process feature. Define each bot under [processes] with a unique name. Each gets its own machine. However, for isolation and independent scaling, separate apps are cleaner.
How do I roll back a broken deployment?
List previous image versions with `flyctl releases`, then redeploy a specific image: `flyctl deploy –image registry.fly.io/your-app:deployment-xxxxx`. Alternatively, revert your code and run `flyctl deploy` again.
Is Fly.io suitable for bots that call the Claude API or OpenAI?
Absolutely. Store your API key via `flyctl secrets set`, read it from the environment in your code, and make outbound HTTPS calls as normal. Fly places no restrictions on outbound traffic. Choose a region close to your AI provider’s endpoints (us-east for Anthropic/OpenAI) to minimize latency.
How much does it cost to run an AI bot on Fly.io 24/7?
The free tier includes 3 shared-cpu-1x VMs with 256 MB RAM. A single always-on bot fits within this. If you exceed the free allowance, a shared-cpu-1x with 256 MB costs approximately $1.94/month. Adding a 1 GB volume adds $0.15/month.
Can I use SQLite on Fly.io?
Yes, but you must attach a persistent volume. Without a volume, the filesystem resets on every deploy. Mount a volume to /data and point your SQLite path there. Note that SQLite on Fly works best with a single machine — concurrent writes from multiple machines will corrupt the database.
How do I update my bot after the initial deploy?
Run flyctl deploy again. Fly builds a new image, performs a rolling update, and routes traffic to the new machine once healthy. For zero-downtime deploys of HTTP bots, Fly handles this automatically. Non-HTTP bots experience a brief restart.
My bot needs to stay running but Fly keeps stopping it. How do I fix this?
Remove the [http_service] block from fly.toml. That block enables auto-stop behavior for HTTP services. For long-running processes, use [processes] to define your command. The machine will stay alive as long as the process runs.
Can I deploy multiple bots in one Fly app?
Yes, using Fly’s multi-process feature. Define each bot under [processes] with a unique name. Each gets its own machine. However, for isolation and independent scaling, separate apps are cleaner.
How do I roll back a broken deployment?
List previous image versions with flyctl releases, then redeploy a specific image: flyctl deploy –image registry.fly.io/your-app:deployment-xxxxx. Alternatively, revert your code and run flyctl deploy again.
Is Fly.io suitable for bots that call the Claude API or OpenAI?
Absolutely. Store your API key via flyctl secrets set, read it from the environment in your code, and make outbound HTTPS calls as normal. Fly places no restrictions on outbound traffic. Choose a region close to your AI provider’s endpoints (us-east for Anthropic/OpenAI) to minimize latency.