Skip to content

Latest commit

 

History

History
348 lines (262 loc) · 6.18 KB

File metadata and controls

348 lines (262 loc) · 6.18 KB

Deployment Guide

Production deployment to Fly.io with optional CDN.

Note: See CLAUDE.md for local development setup.

Prerequisites

  • Fly.io account (signup at fly.io)
  • Fly CLI installed (brew install flyctl)
  • Git repository
  • 1Password CLI (for local secrets)

Initial Setup

1. Install Fly CLI

brew install flyctl
fly auth login

2. Create Fly App

# From project root
fly launch

# Follow prompts:
# - App name: droodotfoo (or your choice)
# - Region: Choose closest to your users
# - PostgreSQL: Yes (required for wiki, pgvector)
# - Redis: No

This creates fly.toml configuration.

3. Set Secrets

# Required secrets
fly secrets set SECRET_KEY_BASE=$(mix phx.gen.secret)
fly secrets set PHX_HOST="your-app.fly.dev"

# Optional: GitHub token (for higher API rate limits)
fly secrets set GITHUB_TOKEN="ghp_your_token_here"

# Optional: Spotify integration
fly secrets set SPOTIFY_CLIENT_ID="your_client_id"
fly secrets set SPOTIFY_CLIENT_SECRET="your_client_secret"

# Optional: CDN
fly secrets set CDN_HOST="your-project.pages.dev"

# Required: Blog API token (for /api/posts endpoint)
fly secrets set BLOG_API_TOKEN=$(mix phx.gen.secret)

Security Note: The BLOG_API_TOKEN is required for the /api/posts endpoint used by Obsidian/external publishing tools. This endpoint features:

  • Bearer token authentication (constant-time comparison)
  • Rate limiting: 10 posts/hour, 50 posts/day per IP
  • Content validation: max 1MB, slug sanitization, path traversal prevention
  • Returns 401 if token not configured (no bypass)

4. Deploy

fly deploy

Configuration

fly.toml

app = "droodotfoo"
primary_region = "sea"

[build]

[env]
  PHX_SERVER = "true"

[http_service]
  internal_port = 4000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0
  processes = ["app"]

[[vm]]
  cpu_kind = "shared"
  cpus = 1
  memory_mb = 256

Custom Domain

# Add custom domain
fly certs add droo.foo

# Check certificate status
fly certs show droo.foo

# DNS configuration (add these records):
# A    @     <fly-ip-address>
# AAAA @     <fly-ipv6-address>

CDN Setup (Optional)

Cloudflare Pages

1. Build Static Assets Locally

mix assets.deploy

2. Deploy to Cloudflare Pages

# Install Wrangler
npm install -g wrangler

# Deploy
cd priv/static
wrangler pages publish . --project-name=droodotfoo

3. Configure Fly.io

fly secrets set CDN_HOST="droodotfoo.pages.dev"

Benefits:

  • Edge caching worldwide
  • Automatic asset optimization
  • DDoS protection
  • No bandwidth charges from Fly.io

Monitoring

Health Checks

Fly.io automatically monitors your app:

# In fly.toml
[[services.http_checks]]
  interval = 10000
  grace_period = "5s"
  method = "get"
  path = "/"
  protocol = "http"
  timeout = 2000

Logs

# Stream logs
fly logs

# Recent logs
fly logs --tail=100

Metrics

# View app status
fly status

# View metrics
fly dashboard

Scaling

Vertical Scaling

# Increase memory
fly scale memory 512

# Increase CPUs
fly scale count 2

Horizontal Scaling

# Add machines in multiple regions
fly scale count 2 --region sea,ord

Auto-scaling

# In fly.toml
[http_service]
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 1
  max_machines_running = 3

Troubleshooting

Common Issues

1. App Won't Start

# Check logs
fly logs

# SSH into machine
fly ssh console

# Check secrets
fly secrets list

2. 502 Bad Gateway

  • Check if app is running: fly status
  • Verify PHX_SERVER=true in environment
  • Check internal port matches (4000)

3. Slow Deployment

# Clear build cache
fly deploy --no-cache

4. Certificate Issues

# Renew certificate
fly certs renew droo.foo

# Check DNS propagation
dig droo.foo

Rollback

# List releases
fly releases

# Rollback to previous
fly releases rollback

CI/CD (Optional)

GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy to Fly.io

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: superfly/flyctl-actions/setup-flyctl@master

      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Setup:

# Get API token
fly auth token

# Add to GitHub secrets:
# Settings → Secrets → Actions → New secret
# Name: FLY_API_TOKEN
# Value: <your-token>

Cost Optimization

Free Tier:

  • 3 shared-cpu-1x VMs (256MB)
  • 160GB bandwidth/month
  • Auto-stop machines when idle

Tips:

  • Use auto_stop_machines = true (stops when idle)
  • Minimize memory (256MB sufficient)
  • Use CDN for static assets
  • Cache aggressively

Security

Best Practices:

  • Use secrets for all sensitive data (never hardcode)
  • Enable force_https
  • Keep dependencies updated
  • Monitor security advisories
  • Rotate secrets periodically (especially BLOG_API_TOKEN)
  • Use strong tokens: mix phx.gen.secret generates secure 64-byte values

Headers:

# In content_security_policy.ex
plug :put_secure_browser_headers, %{
  "content-security-policy" => "...",
  "x-frame-options" => "DENY",
  "x-content-type-options" => "nosniff"
}

Backup Strategy

Git-tracked content:

  • Blog posts in Git (priv/posts/*.md)
  • Resume data in Git (priv/resume.json)
  • Configuration in Git
  • Secrets in 1Password + Fly.io Secrets

Database:

  • PostgreSQL with pgvector (wiki articles, OSRS data, parts catalog)
  • Daily backups to MinIO via Droodotfoo.Wiki.Backup.PostgresWorker (3am)
  • Run mix ecto.setup to recreate schema, mix wiki sync --full to repopulate

Disaster Recovery:

# Redeploy from scratch
fly launch
fly secrets set ...  # Restore secrets
fly deploy           # Deploy from Git
mix ecto.setup       # Create database schema
mix wiki sync --full # Repopulate wiki data

Resources