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…
- 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 arequirements.txt) - A Fly.io account (credit card required for verification, not charged on free tier)
flyctlCLI installed locally- Your AI provider API key (OpenAI, Anthropic, etc.)
- Optional: a custom domain for webhook endpoints
Step 1: Install flyctl and Authenticate
# 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
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")
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"
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
Deploy, attach a volume for persistence, inject secrets as env vars, and verify via logs. That’s the entire workflow.