This guide covers deploying Lion Reader to Fly.io, including provisioning all required infrastructure.
- Prerequisites
- Initial Setup
- Database Provisioning (Postgres)
- Redis Provisioning (Upstash)
- Configuring Secrets
- GitHub Actions Setup
- First Deployment
- Verification
- Ongoing Operations
- Troubleshooting
Before you begin, ensure you have:
Create a free account at fly.io/app/sign-up.
Install the Fly.io CLI:
# macOS
brew install flyctl
# Linux
curl -L https://fly.io/install.sh | sh
# Windows (PowerShell)
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"Verify installation:
flyctl versionflyctl auth loginThis will open a browser window for authentication.
Ensure your code is pushed to a GitHub repository for CI/CD.
From the project root directory, run:
flyctl launch --no-deployWhen prompted:
- App name: Choose a unique name (e.g.,
lion-readerorlion-reader-prod) - Region: Select your preferred region (e.g.,
iadfor US East,lhrfor London) - Postgres: Select "No" (we'll provision this separately for more control)
- Redis: Select "No" (we'll use Upstash)
This creates the app on Fly.io and updates your fly.toml with the app name.
flyctl apps listYou should see your new app listed.
Lion Reader uses PostgreSQL for all persistent data.
flyctl postgres create \
--name lion-reader-db \
--region iad \
--vm-size shared-cpu-1x \
--initial-cluster-size 1 \
--volume-size 10Options explained:
--name: Name for your Postgres app (must be unique)--region: Should match your app'sprimary_regioninfly.toml--vm-size:shared-cpu-1xis sufficient for MVP (~$7/month)--initial-cluster-size: 1 for MVP, increase for HA--volume-size: 10GB is plenty for MVP
flyctl postgres attach lion-reader-db --app lion-readerThis automatically:
- Creates a database user for your app
- Sets the
DATABASE_URLsecret on your app - Configures network access between your app and database
# Connect to the database
flyctl postgres connect -a lion-reader-db
# Run a quick test
\conninfo
\qflyctl secrets list --app lion-readerYou should see DATABASE_URL listed (value is hidden).
Lion Reader uses Redis for session caching, rate limiting, and pub/sub for real-time updates.
Fly.io offers managed Upstash Redis:
flyctl redis createWhen prompted:
- Name:
lion-reader-redis - Region: Same as your app (e.g.,
iad) - Plan: Free tier is fine for MVP (100 commands/day limit)
- For production, choose "Pay-as-you-go" (~$0.20/1M commands)
- Eviction: Enable if you want auto-cleanup of old data
After creation, attach it to your app:
flyctl redis connectCopy the connection string and set it as a secret:
flyctl secrets set REDIS_URL="redis://default:password@fly-lion-reader-redis.upstash.io:6379"- Go to console.upstash.com
- Create a new Redis database
- Select a region close to your Fly.io region
- Copy the Redis URL (TLS format recommended)
- Set the secret:
flyctl secrets set REDIS_URL="rediss://default:password@your-endpoint.upstash.io:6379"Note: Use rediss:// (with double 's') for TLS connections.
Lion Reader requires several secrets for production.
flyctl secrets set NEXT_PUBLIC_APP_URL="https://lionreader.com"Replace with your custom domain if you have one.
flyctl secrets listYou should see:
DATABASE_URL(set automatically by postgres attach)REDIS_URLNEXT_PUBLIC_APP_URL(optional)
| Secret | Required | Description | How to Get |
|---|---|---|---|
DATABASE_URL |
Yes | Postgres connection string | Set by fly postgres attach |
REDIS_URL |
Yes | Redis/Upstash connection string | From Upstash console or fly redis create |
NEXT_PUBLIC_APP_URL |
No | Public URL for the app | Your Fly.io URL or custom domain |
The repository includes CI/CD workflows that automatically deploy on push to master.
flyctl tokens create deploy -x 999999hThis creates a long-lived deploy token. Copy the token value.
- Go to your GitHub repository
- Navigate to Settings > Secrets and variables > Actions
- Click New repository secret
- Name:
FLY_API_TOKEN - Value: Paste the token from step 1
- Click Add secret
The deployment workflow is already configured in .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [master]
workflow_dispatch: # Allow manual trigger
jobs:
deploy:
name: Deploy to Fly.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}For the first deployment, deploy manually to verify everything works:
flyctl deployThis will:
- Build the Docker image on Fly.io's remote builders
- Run database migrations (via
release_commandinfly.toml) - Start your application
- Run health checks
# Watch logs during deployment
flyctl logs
# Check deployment status
flyctl status# Open the app in your browser
flyctl open
# Or check the health endpoint
curl https://lionreader.com/api/healthExpected response:
{
"status": "healthy",
"timestamp": "2024-01-15T12:00:00.000Z"
}After deployment, verify the application works end-to-end:
curl https://your-app.com/api/health- Open
https://your-app.com - Click "Register" or navigate to
/register - Create an account with a test email and password
- Verify you're redirected to the main app
- Click the "+ Subscribe" button
- Enter a test feed URL, for example:
https://feeds.bbci.co.uk/news/rss.xml(BBC News)https://xkcd.com/atom.xml(XKCD)https://blog.cloudflare.com/rss/(Cloudflare Blog)
- Preview the feed and confirm subscription
- Verify entries appear after a few moments
- Entries load in the feed list
- Clicking an entry shows full content
- Mark read/unread works
- Starring entries works
- Sidebar shows unread counts
- Real-time updates work (new entries appear without refresh)
flyctl logs --app lion-readerLook for any errors or warnings.
Increase VM resources:
flyctl scale vm shared-cpu-2x --memory 1024Add more instances:
flyctl scale count 2Connect to database:
flyctl postgres connect -a lion-reader-dbManual backup:
flyctl postgres backup create -a lion-reader-dbList backups:
flyctl postgres backup list -a lion-reader-db# Live logs
flyctl logs
# Recent logs
flyctl logs --no-tail
# Filter by type
flyctl logs --instance <instance-id>flyctl ssh consoleflyctl apps restart lion-reader- Add your domain:
flyctl certs create yourdomain.com- Follow the DNS instructions provided
- Verify certificate:
flyctl certs show yourdomain.com"Release command failed"
This usually means database migrations failed.
# Check logs for migration errors
flyctl logs | grep -i migration
# Connect to database and check state
flyctl postgres connect -a lion-reader-db"Health check failed"
The app isn't responding on /api/health.
# Check app logs
flyctl logs
# SSH and check process
flyctl ssh console
ps aux | grep node"Connection refused"
Ensure the database is attached:
flyctl secrets list
# Should show DATABASE_URL
# Re-attach if needed
flyctl postgres attach lion-reader-db"Authentication failed"
The database user credentials may be wrong. Detach and reattach:
flyctl postgres detach lion-reader-db
flyctl postgres attach lion-reader-db"Connection timeout"
Check if Redis is accessible:
# Verify secret is set
flyctl secrets list | grep REDIS
# Check if using correct protocol (redis:// vs rediss://)For Upstash, ensure you're using TLS (rediss://).
"500 Internal Server Error"
Check logs for the actual error:
flyctl logs --no-tail | tail -100Common causes:
- Missing environment variables
- Database connection issues
- Redis connection issues
"Out of memory"
Scale up the VM:
flyctl scale vm shared-cpu-1x --memory 1024The app uses auto_stop_machines = "stop" which stops idle machines. First request after idle period is slow.
To keep at least one machine always running:
flyctl scale count 1 --min 1Or in fly.toml:
[http_service]
min_machines_running = 1 Internet
|
Fly.io Edge
|
+-----------+-----------+
| |
+------+------+ +------+------+
| App Server | | App Server |
| (Next.js) | | (replica) |
+------+------+ +------+------+
| |
+-----------+-----------+
|
+--------------+--------------+
| |
+------+------+ +-------+------+
| Postgres | | Redis |
| (Fly.io PG) | | (Upstash) |
+-------------+ +--------------+
| Resource | Size | Estimated Cost |
|---|---|---|
| App VM | shared-cpu-1x, 512MB | ~$5/month |
| Postgres | shared-cpu-1x, 10GB | ~$7/month |
| Redis (Upstash) | Pay-as-you-go | ~$0-5/month |
| Total | ~$12-17/month |
Costs vary by usage. Check fly.io/docs/about/pricing for current rates.
After successful deployment:
- Set up monitoring - Configure Sentry for error tracking (see task 8.4)
- Add custom domain - Use
flyctl certs createfor SSL - Enable backups - Configure automated Postgres backups
- Monitor usage - Use Fly.io dashboard to track resource usage
For questions or issues, check: