Deploy on Fly.io
May 17, 2026 · View on GitHub
Two Fly Machines (server + worker), one managed Postgres app, one
Upstash Redis. Suitable for small/medium installs (free tier covers
hobby use; ~$5/mo with persistent Postgres).
1. Prereqs
brew install flyctl # or curl -L https://fly.io/install.sh | sh
fly auth login
2. Postgres
fly postgres create --name devpinger-db --region fra --vm-size shared-cpu-1x
# Copy the DATABASE_URL it prints — you'll need it.
3. Redis
Upstash free tier works fine, or Fly Redis:
fly redis create # follow prompts, choose region
# Copy the rediss:// URL.
4. Create fly.toml for the server
app = "devpinger-server"
primary_region = "fra"
[build]
dockerfile = "apps/server/Dockerfile"
[env]
NODE_ENV = "production"
PORT = "3001"
[http_service]
internal_port = 3001
force_https = true
auto_stop_machines = false
auto_start_machines = true
min_machines_running = 1
[[http_service.checks]]
interval = "30s"
timeout = "5s"
grace_period = "20s"
method = "GET"
path = "/health"
5. Create fly.worker.toml for the worker
app = "devpinger-worker"
primary_region = "fra"
[build]
dockerfile = "apps/worker/Dockerfile"
[env]
NODE_ENV = "production"
[processes]
worker = "pnpm exec tsx src/index.ts"
(Workers don't expose HTTP — no http_service block.)
6. Set secrets (shared between both apps)
fly secrets set \
DATABASE_URL=postgres://… \
REDIS_URL=rediss://… \
TELEGRAM_BOT_TOKEN=… \
TELEGRAM_BOT_USERNAME=dev_pinger_bot \
TELEGRAM_WEBHOOK_SECRET=… \
ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))") \
GITHUB_OAUTH_CLIENT_ID=… \
GITHUB_OAUTH_CLIENT_SECRET=… \
GITHUB_OAUTH_REDIRECT_URI=https://devpinger-server.fly.dev/oauth/github/callback \
JIRA_OAUTH_CLIENT_ID=… \
JIRA_OAUTH_CLIENT_SECRET=… \
JIRA_OAUTH_REDIRECT_URI=https://devpinger-server.fly.dev/oauth/jira/callback \
PUBLIC_BASE_URL=https://devpinger-server.fly.dev \
--app devpinger-server
# Repeat for worker (it needs DATABASE_URL, REDIS_URL, ENCRYPTION_KEY,
# TELEGRAM_BOT_TOKEN at minimum):
fly secrets set DATABASE_URL=… REDIS_URL=… ENCRYPTION_KEY=… \
TELEGRAM_BOT_TOKEN=… --app devpinger-worker
7. Run migrations once
fly ssh console --app devpinger-server -C "pnpm --filter @devpinger/db migrate"
8. Deploy
fly deploy --app devpinger-server --config fly.toml
fly deploy --app devpinger-worker --config fly.worker.toml
9. Verify
curl https://devpinger-server.fly.dev/health
fly logs --app devpinger-server
fly logs --app devpinger-worker
Notes
auto_stop_machines = falseis intentional — webhooks must wake the server within Telegram's 5s SLA. Fly's cold starts are ~1-2s, but for reliability keep at least one machine running.- Worker has no public endpoint, so cold starts don't matter there; you
can set
auto_stop_machines = trueon it if you want. - Scale workers horizontally:
fly scale count 2 --app devpinger-worker. BullMQ handles job distribution.