Skip to main content

Deploy Node.js Apps to Fly.io with Persistent Storage

Fly.io makes deploying Node.js apps incredibly simple. This guide shows you how to deploy apps with persistent storage for SQLite databases and file uploads.

Why Fly.io?

  • Auto-generated Dockerfiles - No Docker knowledge needed
  • Generous free tier - Up to 3 VMs and 3GB storage
  • Built-in SSL - Free HTTPS certificates
  • Simple pricing - Pay only for what you use

Prerequisites

# Install flyctl CLI
curl -L https://fly.io/install.sh | sh

# Login to Fly.io
flyctl auth login

Deploy Your App

1. Initialize

# Run in your project directory
flyctl launch

This will:

  • Auto-generate an optimized Dockerfile
  • Create a fly.toml configuration file
  • Auto-select closest region based on your location
  • Ask if you want to deploy now (say no for now)

Note on regions: Fly.io automatically chooses the closest region to you (e.g., iad for Washington DC if you're on the US East Coast). This gives the best performance for your location. You can manually change it in fly.toml if needed:

primary_region = "iad"  # Washington DC
# primary_region = "lax"  # Los Angeles
# primary_region = "lhr"  # London
# primary_region = "syd"  # Sydney

2. Add Persistent Storage (Optional)

If your app uses SQLite or stores files:

# Create a volume for all persistent data
flyctl volumes create data --size 3 --region iad

Note: Use the same region as your primary_region in fly.toml.

Then add to your fly.toml:

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

Update your app to use /data:

// Use /data for everything persistent
const DB_PATH = process.env.DB_PATH || '/data/app.db';
const UPLOAD_DIR = process.env.UPLOAD_DIR || '/data/uploads';

3. Set Environment Variables

Create a .env file with your secrets:

SMTP_HOST=smtp.example.com
[email protected]
SMTP_PASSWORD=your-password
JWT_SECRET=your-secret-key
DB_PATH=/data/app.db

Pipe it directly to Fly.io secrets:

cat .env | flyctl secrets import

This makes editing easier - just update .env and re-import.

4. Deploy

flyctl deploy

That's it! Your app is live.

Example fly.toml Configuration

app = "your-app-name"
primary_region = "iad"  # Auto-selected based on your location

[build]

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

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "stop"  # Stop when idle (free tier)
  auto_start_machines = true   # Start on request
  min_machines_running = 0

[[vm]]
  memory = "512mb"  # 256mb works fine for very small apps
  cpu_kind = "shared"
  cpus = 1

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

RAM sizing guide:

  • 256MB - Very small apps (simple APIs, webhooks)
  • 512MB - Most Node.js apps (recommended starting point)
  • 1GB+ - Heavy workloads (large datasets, image processing)

Health Checks

Add a simple health endpoint:

fastify.get('/health', async (request, reply) => {
  return { status: 'ok' };
});

Configure in fly.toml:

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

Monitoring

# View live logs
flyctl logs

# SSH into machine
flyctl ssh console

# Check app status
flyctl status

# Restart app
flyctl apps restart

Increasing Resources

As your app grows, you can easily add more resources:

Add More Storage

# Increase volume size
flyctl volumes extend <volume-id> --size 10

Add More Memory

# Scale to 1GB RAM
flyctl scale memory 1024

Add More CPU

# Scale to 2 CPUs
flyctl scale count 2

Backups

# Create volume snapshot
flyctl volumes snapshots create <volume-id>

# List snapshots
flyctl volumes snapshots list <volume-id>

# Restore from snapshot
flyctl volumes create data --snapshot-id <snapshot-id>

Free Tier Tips

Your fly.toml is already optimized for the free tier:

  • auto_stop_machines = "stop" - Stops when not in use
  • auto_start_machines = true - Starts on request
  • min_machines_running = 0 - Allows complete shutdown
  • memory = "512mb" - Fits within free tier (or use 256mb for smaller apps)

Free tier includes:

  • 3 shared-cpu VMs (256MB RAM each)
  • 3GB persistent storage
  • 160GB outbound data transfer

Common Issues

Port Not Binding Correctly

// IMPORTANT: Bind to 0.0.0.0, not localhost
const PORT = parseInt(process.env.PORT || '3000', 10);
fastify.listen({ port: PORT, host: '0.0.0.0' });

Build Failures

If your build needs more memory:

npx @flydotio/dockerfile --swap=1024
flyctl deploy

Database Migrations

Run migrations via SSH:

flyctl ssh console -C "node dist/migrate.js"

Custom Domain

# Add your domain
flyctl certs create yourdomain.com

# Follow instructions to add DNS record
flyctl certs show yourdomain.com

Complete Example

Here's the full workflow for a support inbox app:

# 1. Initialize (auto-selects closest region)
cd services/support-inbox
flyctl launch --no-deploy

# 2. Create volume (use same region as fly.toml)
flyctl volumes create data --size 3 --region iad

# 3. Add volume mount to fly.toml
# [mounts]
#   source = "data"
#   destination = "/data"

# 4. Set environment variables
cat .env | flyctl secrets import

# 5. Deploy
flyctl deploy

# 6. Check logs
flyctl logs

Useful Commands

# View all apps
flyctl apps list

# View app info
flyctl info

# View secrets
flyctl secrets list

# Update a secret
echo "NEW_VALUE" | flyctl secrets set SECRET_NAME=-

# View metrics
flyctl dashboard

# Destroy app (when done)
flyctl apps destroy your-app-name

Resources


That's it! Your Node.js app is deployed with:

✅ Auto-generated Docker configuration
✅ Auto-selected optimal region
✅ Persistent storage for data
✅ Free HTTPS certificates
✅ Easy environment variable management
✅ Simple resource scaling when needed

Deploy in under 5 minutes with flyctl launch and flyctl deploy.

Need help with your project or have questions?

We specialize in AI automation, custom integrations, and intelligent workflows tailored to your business needs.

Whether you need help deploying, building, implementing, or creating a solution - or just want expert guidance on your project - we're here to help.

Contact us today to discuss your project.