This commit is contained in:
2026-07-31 11:47:31 +09:00
parent d338b91e42
commit 3271693051
14 changed files with 3024 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
# LinkForty Production Environment Configuration
#
# This file shows all environment variables needed for production deployment on Fly.io
#
# SECURITY WARNING: Never commit actual secrets to version control!
# Use `fly secrets set` to configure these values securely.
#
# Example:
# fly secrets set DATABASE_URL="postgresql://..."
# fly secrets set REDIS_URL="rediss://..."
# ============================================================================
# REQUIRED: Database Configuration
# ============================================================================
# PostgreSQL connection string
# Format: postgresql://username:password@host:port/database?sslmode=require
#
# For Fly Postgres (automatically set by `fly postgres attach`):
# postgresql://user:pass@appname.internal:5432/dbname?sslmode=require
#
# For external providers (Supabase, Neon, AWS RDS):
# Use connection string from provider dashboard
#
# IMPORTANT: Always include ?sslmode=require for production
DATABASE_URL="postgresql://username:password@host:5432/linkforty?sslmode=require"
# ============================================================================
# OPTIONAL BUT RECOMMENDED: Redis Cache
# ============================================================================
# Redis connection string (TLS encrypted)
# Format: rediss://default:password@host:6379
#
# For Upstash Redis (via Fly.io):
# Run: fly redis create
# Use the REDIS_URL provided
#
# IMPORTANT: Use rediss:// (double 's') for TLS encryption in production
#
# If not set, LinkForty will work but without caching (slower, higher DB load)
REDIS_URL="rediss://default:password@host:6379"
# ============================================================================
# APPLICATION CONFIGURATION
# ============================================================================
# Environment mode (should always be "production" for deployed apps)
NODE_ENV="production"
# Server port (default: 8080, must match fly.toml internal_port)
PORT="8080"
# ============================================================================
# CORS CONFIGURATION
# ============================================================================
# Allowed origins for CORS (comma-separated for multiple origins)
#
# Single origin:
# CORS_ORIGIN="https://yourdomain.com"
#
# Multiple origins:
# CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com,https://www.yourdomain.com"
#
# SECURITY WARNING: Never use "*" in production - only specific domains!
CORS_ORIGIN="https://yourdomain.com"
# ============================================================================
# OPTIONAL: Advanced Configuration
# ============================================================================
# Log level (error, warn, info, debug)
# Default: "info"
# Use "error" or "warn" in production to reduce log volume
LOG_LEVEL="info"
# Request timeout in milliseconds
# Default: 30000 (30 seconds)
# REQUEST_TIMEOUT="30000"
# Database connection pool settings
# Default min: 2, max: 10
# Adjust based on your database plan and expected load
# DB_POOL_MIN="2"
# DB_POOL_MAX="10"
# Redis cache TTL (time-to-live) in seconds
# Default: 3600 (1 hour)
# Higher values = less DB load, potentially stale data
# Lower values = more accurate, higher DB load
# REDIS_TTL="3600"
# ============================================================================
# CUSTOM DOMAIN (Optional)
# ============================================================================
# If using a custom domain for short links
# Example: If your short links should be https://go.yourdomain.com/abc123
# Set this to match your domain configuration
#
# See: https://fly.io/docs/app-guides/custom-domains-with-fly/
# CUSTOM_DOMAIN="go.yourdomain.com"
# ============================================================================
# MONITORING & OBSERVABILITY (Optional)
# ============================================================================
# Sentry DSN for error tracking
# Sign up at https://sentry.io and get your DSN
# SENTRY_DSN="https://...@sentry.io/..."
# New Relic license key
# NEW_RELIC_LICENSE_KEY="..."
# Datadog API key
# DATADOG_API_KEY="..."
# ============================================================================
# DEPLOYMENT CHECKLIST
# ============================================================================
#
# Before deploying to production, ensure:
#
# [ ] DATABASE_URL is set with sslmode=require
# [ ] REDIS_URL is set (or intentionally omitted)
# [ ] CORS_ORIGIN is set to your actual domain(s), NOT "*"
# [ ] NODE_ENV is set to "production"
# [ ] All secrets are set via `fly secrets set`, not in fly.toml
# [ ] You've reviewed SECURITY.md checklist
# [ ] Database migrations have been tested
# [ ] Health check endpoint (/health) works
# [ ] You have database backups configured
#
# Set secrets with:
# fly secrets set DATABASE_URL="..." --app your-app
# fly secrets set REDIS_URL="..." --app your-app
# fly secrets set CORS_ORIGIN="..." --app your-app
#
# Verify secrets (values are hidden):
# fly secrets list --app your-app
#
# ============================================================================
+372
View File
@@ -0,0 +1,372 @@
# Deploying LinkForty to Fly.io
This guide walks you through deploying LinkForty to [Fly.io](https://fly.io), a global application platform that makes deployment simple.
## Prerequisites
1. **Fly.io Account**
- Sign up at https://fly.io/app/sign-up
- Credit card required (but has generous free tier)
2. **Fly CLI Installed**
```bash
# macOS/Linux
curl -L https://fly.io/install.sh | sh
# Windows
iwr https://fly.io/install.ps1 -useb | iex
```
3. **Authenticate**
```bash
fly auth login
```
4. **LinkForty Built Locally**
```bash
npm install
npm run build
```
## Step 1: Customize Configuration
1. Edit `infra/fly.io/fly.toml`:
```toml
app = "your-unique-app-name" # Must be globally unique
primary_region = "iad" # Choose your region
```
2. Available regions (run `fly platform regions` for full list):
- `iad` - Washington DC (US East)
- `lax` - Los Angeles (US West)
- `lhr` - London (Europe)
- `fra` - Frankfurt (Europe)
- `syd` - Sydney (Asia-Pacific)
- `nrt` - Tokyo (Asia-Pacific)
## Step 2: Create PostgreSQL Database
LinkForty requires PostgreSQL 13+.
```bash
# Create a Postgres cluster
fly postgres create --name linkforty-db --region iad
# Choose configuration when prompted:
# - Development: 1GB RAM, 10GB storage (free tier eligible)
# - Production: 2GB+ RAM, 20GB+ storage
# Attach database to your app
fly postgres attach linkforty-db --app your-unique-app-name
```
This automatically sets the `DATABASE_URL` secret in your app.
**Alternative:** Use [Supabase](https://supabase.com) or another managed PostgreSQL provider and set `DATABASE_URL` manually (see Step 5).
📖 Detailed PostgreSQL setup: [fly.postgres.md](./fly.postgres.md)
## Step 3: Create Redis Cache (Optional but Recommended)
Redis improves performance by ~90% for repeated link lookups.
```bash
# Create Upstash Redis (Fly's Redis partner)
fly redis create --name linkforty-redis --region iad
# Choose plan when prompted:
# - Development: Free tier (256MB)
# - Production: Eviction-$10 or Eviction-$40
# Note the REDIS_URL provided - you'll set it in Step 5
```
📖 Detailed Redis setup: [fly.redis.md](./fly.redis.md)
## Step 4: Create Your Fly.io App
From the root of your LinkForty project:
```bash
# Copy fly.toml to root directory
cp infra/fly.io/fly.toml fly.toml
# Create the app (don't deploy yet)
fly apps create your-unique-app-name --org personal
```
## Step 5: Set Secrets (Environment Variables)
```bash
# DATABASE_URL (automatically set if you used fly postgres attach)
# If using external PostgreSQL:
fly secrets set DATABASE_URL="postgresql://user:password@host:5432/dbname?sslmode=require"
# REDIS_URL (from Step 3 output)
fly secrets set REDIS_URL="redis://default:password@host:6379"
# CORS Origin (your frontend domain)
fly secrets set CORS_ORIGIN="https://yourdomain.com"
# Optional: Custom port (defaults to 8080)
fly secrets set PORT="8080"
# View configured secrets (values are hidden)
fly secrets list
```
⚠️ **Security Note:** Never commit secrets to git. Use `fly secrets set` only.
## Step 6: Initial Deployment
```bash
# Deploy from the root directory (where fly.toml is located)
fly deploy
# This will:
# 1. Build your Docker image
# 2. Push to Fly.io registry
# 3. Run migrations (via release_command in fly.toml)
# 4. Deploy to your chosen region(s)
# 5. Start health checks
```
**First deployment takes 3-5 minutes.** Subsequent deployments are faster.
## Step 7: Verify Deployment
```bash
# Check app status
fly status
# View recent logs
fly logs
# Open your app in browser
fly open
# Test the health endpoint
curl https://your-app.fly.dev/health
```
## Step 8: Create Your First Link
```bash
# Using curl
curl -X POST https://your-app.fly.dev/api/links \
-H "Content-Type: application/json" \
-d '{
"userId": "user-123",
"iosUrl": "myapp://product/123",
"androidUrl": "myapp://product/123",
"webUrl": "https://mysite.com/product/123"
}'
# Response includes your short code
# {"code":"abc123","shortUrl":"https://your-app.fly.dev/abc123"}
```
Test the redirect:
```bash
curl -L https://your-app.fly.dev/abc123
```
## Scaling Your Deployment
### Vertical Scaling (More Resources Per Machine)
Edit `fly.toml`:
```toml
[vm]
cpu_kind = "shared" # or "performance" for dedicated CPUs
cpus = 2
memory_mb = 512 # or 1024, 2048, etc.
```
Then deploy:
```bash
fly deploy
```
### Horizontal Scaling (More Machines)
```bash
# Scale to 3 machines in primary region
fly scale count 3
# Or use auto-scaling (edit fly.toml first)
# Uncomment the [[services.autoscaling]] section
fly deploy
```
### Multi-Region Deployment
Edit `fly.toml` to uncomment regions:
```toml
[[regions]]
name = "iad" # US East
[[regions]]
name = "lhr" # Europe
[[regions]]
name = "nrt" # Asia
```
Deploy:
```bash
fly deploy
```
Fly.io automatically routes users to the nearest region.
## Database Migrations
Migrations run automatically on deploy (via `release_command` in fly.toml).
To run manually:
```bash
# SSH into a machine
fly ssh console
# Run migrations
npm run migrate
# Exit
exit
```
## Monitoring and Logs
```bash
# Real-time logs
fly logs
# Filter by app instance
fly logs -i instance-id
# View metrics dashboard
fly dashboard
# Check machine status
fly status
# View recent deployments
fly releases
```
## Troubleshooting
### Deployment Fails
```bash
# Check build logs
fly logs --app your-app
# Verify secrets are set
fly secrets list
# Check app configuration
fly config show
```
### Health Checks Failing
Ensure your app exposes `/health` endpoint:
```typescript
// In your Fastify setup
fastify.get('/health', async (request, reply) => {
return { status: 'ok' };
});
```
Check `fly.toml` health check path matches.
### Database Connection Issues
```bash
# Verify DATABASE_URL is set
fly secrets list
# Check database status
fly postgres status linkforty-db
# View database connection info
fly postgres db list linkforty-db
```
### Out of Memory Errors
Increase memory allocation in `fly.toml`:
```toml
[vm]
memory_mb = 512 # Increase from 256
```
### High Latency
Consider:
- Enabling Redis cache (see Step 3)
- Multi-region deployment
- Increasing machine resources
- Database connection pooling (already configured)
## Updating Your Deployment
```bash
# After making code changes:
npm run build
fly deploy
# To rollback to previous version:
fly releases
fly releases rollback <version>
```
## Cost Optimization
**Free Tier Eligible Setup:**
- 1x shared-cpu-1x machine (256MB) = Free
- Fly Postgres (1GB, development tier) = Free
- Upstash Redis (256MB) = Free
- **Total: $0/month** for low-traffic apps
**Small Production Setup (~$10-15/month):**
- 1x shared-cpu-1x (512MB) = ~$3.50/month
- Fly Postgres (2GB) = ~$7/month
- Upstash Redis (Eviction-$10) = ~$10/month
- **Total: ~$20/month**
**View your costs:**
```bash
fly billing show
```
## Security Checklist
Before going to production, review [SECURITY.md](./SECURITY.md) for:
- Environment variable security
- Database SSL configuration
- CORS settings
- Secret rotation
- Backup strategy
## Next Steps
- Set up custom domain: https://fly.io/docs/app-guides/custom-domains-with-fly/
- Configure TLS certificates: Automatic with Fly.io
- Set up monitoring alerts: https://fly.io/docs/reference/metrics/
- Enable database backups: See [fly.postgres.md](./fly.postgres.md)
## Getting Help
- **Fly.io Docs:** https://fly.io/docs/
- **Fly.io Community:** https://community.fly.io/
- **LinkForty Issues:** https://github.com/yourusername/linkforty-core/issues
- **Security Issues:** See [SECURITY.md](../../SECURITY.md)
## Resources
- [Fly.io Pricing](https://fly.io/docs/about/pricing/)
- [Fly.io Regions](https://fly.io/docs/reference/regions/)
- [Fly.io Node.js Guide](https://fly.io/docs/languages-and-frameworks/node/)
- [Fly.io PostgreSQL](https://fly.io/docs/postgres/)
+391
View File
@@ -0,0 +1,391 @@
# Security Checklist for Production Deployment
This checklist helps ensure your LinkForty deployment on Fly.io follows security best practices.
## ✅ Pre-Deployment Security Checklist
### Environment Variables & Secrets
- [ ] All sensitive values are set using `fly secrets set` (never in `fly.toml`)
- [ ] `DATABASE_URL` includes `?sslmode=require` for PostgreSQL
- [ ] `REDIS_URL` includes authentication credentials
- [ ] `CORS_ORIGIN` is set to your actual frontend domain(s), not `*`
- [ ] `NODE_ENV` is set to `"production"`
- [ ] No secrets are committed to version control
- [ ] `.env` files are in `.gitignore`
### Database Security
- [ ] PostgreSQL uses SSL/TLS connections (`sslmode=require`)
- [ ] Database password is strong (20+ characters, random)
- [ ] Database is not publicly accessible (Fly Postgres is private by default)
- [ ] Connection pooling is configured (default: 2-10 connections)
- [ ] Database backups are enabled (see [fly.postgres.md](./fly.postgres.md))
- [ ] Backup retention policy is configured
### Redis Security
- [ ] Redis requires authentication (Upstash Redis includes this by default)
- [ ] Redis connection uses TLS (`rediss://` protocol)
- [ ] Redis is not publicly accessible
- [ ] Connection timeout is configured
### Application Security
- [ ] CORS is properly configured (not set to `*` in production)
- [ ] Rate limiting is enabled for link creation endpoints
- [ ] Input validation is active (Zod schemas)
- [ ] SQL injection protection via parameterized queries (pg library handles this)
- [ ] Health check endpoint (`/health`) exposes no sensitive data
- [ ] Error messages don't leak sensitive information
- [ ] Logging doesn't include secrets or PII
### Network Security
- [ ] HTTPS is enforced (`force_https = true` in fly.toml)
- [ ] HTTP is redirected to HTTPS
- [ ] Health checks use HTTPS
- [ ] No sensitive services are exposed publicly
### Access Control
- [ ] Fly.io account uses strong password + 2FA
- [ ] Fly.io organization access is limited to required team members
- [ ] Deploy tokens (if used in CI/CD) have minimum required permissions
- [ ] Database credentials are rotated regularly (quarterly minimum)
---
## 🔐 Security Configuration Details
### 1. Database URL Security
Your `DATABASE_URL` should look like:
```
postgresql://user:password@host:5432/dbname?sslmode=require
```
Key requirements:
- `sslmode=require` - Forces SSL/TLS encryption
- Strong password (20+ characters)
- Host should be internal Fly network (`.internal` domain) if using Fly Postgres
Set it securely:
```bash
fly secrets set DATABASE_URL="postgresql://user:password@host:5432/dbname?sslmode=require"
```
### 2. Redis URL Security
Your `REDIS_URL` should look like:
```
rediss://default:password@host:6379
```
Key requirements:
- `rediss://` protocol (TLS encrypted)
- Authentication password included
- Upstash Redis (recommended) includes TLS by default
Set it securely:
```bash
fly secrets set REDIS_URL="rediss://default:password@host:6379"
```
### 3. CORS Configuration
For production, set specific origins:
```bash
# Single origin
fly secrets set CORS_ORIGIN="https://yourdomain.com"
# Multiple origins (comma-separated)
fly secrets set CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
```
Never use `*` in production - this allows any website to make requests to your API.
### 4. Rate Limiting
LinkForty includes built-in rate limiting. Verify it's enabled in your deployment:
- Link creation: Limited by IP address
- Analytics queries: Limited by userId
- Redirect endpoints: Unlimited (by design for fast redirects)
Monitor rate limit metrics:
```bash
fly logs | grep "rate limit"
```
### 5. Input Validation
LinkForty uses Zod for input validation. Ensure validation errors are logged:
```bash
fly logs | grep "validation"
```
Common validation issues to monitor:
- Invalid URLs
- Malformed userId
- Invalid UTM parameters
- Expired links
---
## 🔄 Secret Rotation Policy
Rotate secrets regularly to minimize compromise risk.
### Quarterly Rotation (Every 3 months)
1. **Database Password**
```bash
# On Fly Postgres
fly postgres connect -a linkforty-db
ALTER USER your_user WITH PASSWORD 'new-strong-password';
\q
# Update secret
fly secrets set DATABASE_URL="postgresql://user:new-password@host:5432/dbname?sslmode=require"
```
2. **Redis Password**
```bash
# Generate new Upstash Redis credentials
# In Upstash dashboard: Reset password
# Update secret
fly secrets set REDIS_URL="rediss://default:new-password@host:6379"
```
3. **API Keys** (if you add API authentication)
```bash
fly secrets set API_KEY="new-random-key"
```
### After Security Incident
Rotate ALL secrets immediately:
- Database credentials
- Redis credentials
- Any API keys
- Fly.io deploy tokens (if compromised)
---
## 🔍 Security Monitoring
### Log Monitoring
Monitor these security-relevant events:
```bash
# Failed database connections
fly logs | grep "connection refused"
# Rate limit hits
fly logs | grep "rate limit"
# Validation errors
fly logs | grep "validation error"
# Suspicious activity patterns
fly logs | grep "error"
```
### Metrics to Watch
- **Unusual traffic spikes** - Potential DDoS or abuse
- **High error rates** - Potential attack or misconfiguration
- **Database connection errors** - Credential issues or network problems
- **Redis connection failures** - Service degradation
Access metrics:
```bash
fly dashboard # Web UI with graphs
fly status # Current health
```
### Automated Alerts (Recommended)
Set up alerts for:
- High error rates (>5% of requests)
- Service downtime
- Database connection failures
- Unusual traffic patterns
Fly.io integrates with:
- Sentry (error tracking)
- Datadog (monitoring)
- Prometheus (metrics)
---
## 🛡️ Incident Response Plan
### If Secrets Are Compromised
1. **Immediately rotate all secrets**
```bash
# See "Secret Rotation Policy" section above
```
2. **Review access logs**
```bash
fly logs --all
```
3. **Check for unauthorized database changes**
```bash
fly postgres connect -a linkforty-db
SELECT * FROM users ORDER BY created_at DESC LIMIT 100;
SELECT * FROM links ORDER BY created_at DESC LIMIT 100;
```
4. **Review Fly.io access logs**
- Check Fly.io dashboard for recent deploys
- Review team member access
- Check for unknown IP addresses
5. **Deploy with new secrets**
```bash
fly deploy
```
6. **Document the incident**
- What was compromised?
- How was it discovered?
- What actions were taken?
- How to prevent in the future?
### If Database Is Compromised
1. **Restore from backup**
```bash
# See fly.postgres.md for backup/restore instructions
```
2. **Analyze what data was accessed/modified**
3. **Notify affected users** (if PII was compromised)
4. **Review and strengthen security measures**
---
## 🔒 Data Protection
### Personal Identifiable Information (PII)
LinkForty collects:
- IP addresses (for geolocation)
- User agent strings (for device detection)
- Referrer URLs (for analytics)
- User IDs (provided by you)
**Your responsibilities:**
- Comply with GDPR, CCPA, or relevant privacy laws
- Implement data retention policies
- Provide user data deletion endpoints
- Maintain privacy policy
- Obtain user consent where required
### Data Retention
Consider implementing:
- Automatic deletion of click events older than X days
- User data export functionality
- Data anonymization for old analytics
Example cleanup query:
```sql
DELETE FROM click_events WHERE created_at < NOW() - INTERVAL '90 days';
```
Schedule via cron or Fly.io scheduled tasks.
### Encryption
- **In transit:** HTTPS/TLS for all connections (enforced by Fly.io)
- **At rest:** Fly Postgres encrypts data at rest automatically
- **Application level:** Consider encrypting sensitive user data in JSONB fields
---
## 🚨 Security Vulnerabilities
### Reporting Security Issues
**DO NOT** open public GitHub issues for security vulnerabilities.
Instead:
- Email security@yourdomain.com (set up a security contact)
- Use GitHub Security Advisories (private disclosure)
- Allow 90 days for responsible disclosure
### Keeping Dependencies Updated
```bash
# Check for vulnerabilities
npm audit
# Fix automatically where possible
npm audit fix
# Review and update dependencies quarterly
npm outdated
npm update
```
Subscribe to security advisories:
- Node.js security releases
- Fastify security updates
- PostgreSQL security announcements
---
## ✅ Security Checklist Summary
Print and complete this checklist before every production deployment:
**Pre-Deploy:**
- [ ] Secrets set via `fly secrets set` (not in code)
- [ ] Database uses SSL (`sslmode=require`)
- [ ] CORS configured for specific domains
- [ ] HTTPS enforced in `fly.toml`
- [ ] No secrets in version control
**Post-Deploy:**
- [ ] Health check passes
- [ ] HTTPS redirect works
- [ ] CORS allows only intended domains
- [ ] Database connection successful
- [ ] Redis connection successful
- [ ] Rate limiting active
- [ ] Error logging working
**Ongoing:**
- [ ] Secrets rotated quarterly
- [ ] Dependencies updated monthly
- [ ] Logs monitored weekly
- [ ] Backups verified monthly
- [ ] Access control reviewed quarterly
---
## 📚 Additional Resources
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Fly.io Security Best Practices](https://fly.io/docs/reference/security/)
- [PostgreSQL Security](https://www.postgresql.org/docs/current/runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SSL)
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
- [Fastify Security](https://www.fastify.io/docs/latest/Guides/Security/)
---
**Last Updated:** 2025-01-13
Review and update this checklist quarterly or after any security incident.
+561
View File
@@ -0,0 +1,561 @@
# PostgreSQL Setup on Fly.io
This guide covers setting up and managing PostgreSQL for LinkForty on Fly.io.
## Overview
LinkForty requires PostgreSQL 13 or higher. Fly.io offers managed PostgreSQL clusters with:
- Automated backups
- Point-in-time recovery
- High availability options
- Built-in monitoring
- Private networking
## Creating a PostgreSQL Cluster
### Option 1: Development/Small Production
For testing or small deployments:
```bash
fly postgres create --name linkforty-db \
--region iad \
--initial-cluster-size 1 \
--vm-size shared-cpu-1x \
--volume-size 10
```
**Configuration:**
- 1 machine (no high availability)
- 1 CPU, 256MB RAM
- 10GB storage
- **Cost:** Free tier eligible
### Option 2: Production with High Availability
For production deployments:
```bash
fly postgres create --name linkforty-db \
--region iad \
--initial-cluster-size 3 \
--vm-size shared-cpu-2x \
--volume-size 20
```
**Configuration:**
- 3 machines (1 primary + 2 replicas)
- 2 CPUs, 512MB RAM per machine
- 20GB storage per machine
- Automatic failover
- **Cost:** ~$20-30/month
### Interactive Creation
Simply run without arguments for interactive prompts:
```bash
fly postgres create
```
The CLI will ask:
1. App name (e.g., `linkforty-db`)
2. Region (choose closest to your app)
3. Configuration (Development vs Production)
## Attaching Database to Your App
After creating the cluster:
```bash
fly postgres attach linkforty-db --app your-linkforty-app
```
This automatically:
- Creates a dedicated database and user for your app
- Sets the `DATABASE_URL` secret in your app
- Configures connection pooling
- Enables SSL connections
## Manual Connection String Setup
If you prefer manual setup or use external PostgreSQL:
```bash
# Get connection details
fly postgres connect -a linkforty-db
# In psql:
CREATE DATABASE linkforty;
CREATE USER linkforty_user WITH ENCRYPTED PASSWORD 'your-strong-password';
GRANT ALL PRIVILEGES ON DATABASE linkforty TO linkforty_user;
\q
# Set the DATABASE_URL secret
fly secrets set DATABASE_URL="postgresql://linkforty_user:your-strong-password@linkforty-db.internal:5432/linkforty?sslmode=require" --app your-linkforty-app
```
## Connection String Format
Your `DATABASE_URL` should follow this format:
```
postgresql://username:password@host:port/database?sslmode=require
```
**Components:**
- `username` - Database user (created by `attach` or manually)
- `password` - Strong password (20+ characters)
- `host` - For Fly Postgres: `appname.internal` (private network)
- `port` - Usually `5432`
- `database` - Database name
- `sslmode=require` - **Required** for secure connections
## Running Migrations
### Automatic (Recommended)
Migrations run automatically on deploy via `fly.toml`:
```toml
[deploy]
release_command = "npm run migrate"
```
Every deployment:
1. Builds your app
2. Runs `npm run migrate` (before starting the app)
3. Deploys if migrations succeed
### Manual Migration
If you need to run migrations manually:
```bash
# SSH into your app
fly ssh console --app your-linkforty-app
# Run migrations
npm run migrate
# Exit
exit
```
Or connect directly to the database:
```bash
# Connect via psql
fly postgres connect -a linkforty-db
# Run SQL manually
\i /path/to/migration.sql
# Or use LinkForty's migration tool
\q
```
## Database Management
### Viewing Database Info
```bash
# Status of Postgres cluster
fly postgres status linkforty-db
# List databases
fly postgres db list linkforty-db
# List users
fly postgres users list linkforty-db
# View connection info
fly postgres config view linkforty-db
```
### Connecting to Database
```bash
# Interactive psql session
fly postgres connect -a linkforty-db
# Once connected:
\l # List databases
\c linkforty # Connect to specific database
\dt # List tables
\d links # Describe table schema
SELECT COUNT(*) FROM links; # Query data
\q # Quit
```
### Monitoring
```bash
# View Postgres logs
fly logs -a linkforty-db
# Check CPU/memory usage
fly status -a linkforty-db
# Dashboard with metrics
fly dashboard linkforty-db
```
## Backups and Recovery
### Automatic Backups
Fly Postgres includes automated backups:
- **Development tier:** Daily backups, 7-day retention
- **Production tier:** Daily backups, 30-day retention
- Point-in-time recovery available
### Manual Backup
```bash
# Create a snapshot
fly volumes snapshots create <volume-id> -a linkforty-db
# List snapshots
fly volumes snapshots list <volume-id> -a linkforty-db
# Get volume ID
fly volumes list -a linkforty-db
```
### Export Database (Manual Backup)
```bash
# Dump entire database
fly postgres connect -a linkforty-db -c "pg_dump linkforty" > backup.sql
# Dump only schema
fly postgres connect -a linkforty-db -c "pg_dump --schema-only linkforty" > schema.sql
# Dump only data
fly postgres connect -a linkforty-db -c "pg_dump --data-only linkforty" > data.sql
```
### Restore from Backup
```bash
# Restore from SQL dump
fly postgres connect -a linkforty-db
# In psql:
DROP DATABASE linkforty; # ⚠️ CAUTION: Deletes all data
CREATE DATABASE linkforty;
\q
# Import dump
cat backup.sql | fly postgres connect -a linkforty-db -d linkforty
```
### Point-in-Time Recovery
Contact Fly.io support for point-in-time recovery:
```bash
fly support create "Need PITR for linkforty-db to <timestamp>"
```
## Scaling PostgreSQL
### Vertical Scaling (More Resources)
```bash
# Scale VM size
fly postgres update linkforty-db --vm-size shared-cpu-2x
# Increase storage
fly volumes extend <volume-id> --size 40 -a linkforty-db
```
Available VM sizes:
- `shared-cpu-1x` - 256MB RAM (free tier)
- `shared-cpu-2x` - 512MB RAM
- `shared-cpu-4x` - 1GB RAM
- `shared-cpu-8x` - 2GB RAM
- `performance-1x` - 2GB RAM (dedicated CPU)
- `performance-2x` - 4GB RAM (dedicated CPU)
### Horizontal Scaling (Read Replicas)
Add replicas for read scaling:
```bash
# Add replica in same region
fly postgres update linkforty-db --add-replica
# Add replica in different region (multi-region)
fly postgres update linkforty-db --add-replica --region lhr
```
**Note:** LinkForty's connection pool handles read/write routing automatically.
### High Availability
For production, use 3-node cluster:
```bash
fly postgres update linkforty-db --initial-cluster-size 3
```
Benefits:
- Automatic failover (30-60 seconds)
- 2 replicas for read scaling
- Higher durability
## Performance Tuning
### Connection Pooling
LinkForty includes connection pooling (configured in `src/database/pool.ts`):
```typescript
// Default configuration
min: 2, // Minimum connections
max: 10, // Maximum connections
```
Adjust based on your VM size:
- 256MB RAM: max 5-10 connections
- 512MB RAM: max 10-20 connections
- 1GB+ RAM: max 20-50 connections
### Indexes
LinkForty creates these indexes automatically (via migrations):
```sql
-- Fast lookups by code
CREATE INDEX idx_links_code ON links(code);
-- User isolation
CREATE INDEX idx_links_user_id ON links(user_id);
-- Analytics queries
CREATE INDEX idx_click_events_link_id ON click_events(link_id);
CREATE INDEX idx_click_events_created_at ON click_events(created_at);
```
### Query Performance
Monitor slow queries:
```bash
fly postgres connect -a linkforty-db
# Enable logging of slow queries (>1s)
ALTER DATABASE linkforty SET log_min_duration_statement = 1000;
# View slow queries in logs
\q
fly logs -a linkforty-db | grep "duration:"
```
### Vacuum and Analyze
PostgreSQL automatically runs `VACUUM` and `ANALYZE`. To run manually:
```bash
fly postgres connect -a linkforty-db
VACUUM ANALYZE links;
VACUUM ANALYZE click_events;
```
## Security
### SSL/TLS Connections
Always use `sslmode=require` in production:
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```
Verify SSL is active:
```bash
fly postgres connect -a linkforty-db
SELECT ssl.* FROM pg_stat_ssl ssl, pg_stat_activity a
WHERE ssl.pid = a.pid AND a.usename = 'linkforty_user';
```
### Password Rotation
Rotate database passwords quarterly:
```bash
fly postgres connect -a linkforty-db
ALTER USER linkforty_user WITH PASSWORD 'new-strong-password';
\q
# Update secret in your app
fly secrets set DATABASE_URL="postgresql://linkforty_user:new-strong-password@linkforty-db.internal:5432/linkforty?sslmode=require" --app your-linkforty-app
```
### Network Isolation
Fly Postgres is only accessible:
- Within your Fly.io private network (`.internal`)
- Via WireGuard VPN (for admin access)
Not exposed to public internet.
## Monitoring and Alerts
### Key Metrics to Monitor
```bash
# Connection count
fly postgres connect -a linkforty-db -c "SELECT count(*) FROM pg_stat_activity;"
# Database size
fly postgres connect -a linkforty-db -c "SELECT pg_size_pretty(pg_database_size('linkforty'));"
# Table sizes
fly postgres connect -a linkforty-db -c "
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
"
```
### Set Up Alerts
Monitor these conditions:
- Storage > 80% full
- Connection pool exhausted
- Replication lag > 10s
- CPU > 80% for 5+ minutes
Use Fly.io monitoring or integrate with:
- Datadog
- New Relic
- Prometheus + Grafana
## Troubleshooting
### Connection Refused
```bash
# Check Postgres status
fly postgres status linkforty-db
# Check DATABASE_URL is correct
fly secrets list --app your-linkforty-app
# Verify Postgres is running
fly logs -a linkforty-db
```
### Out of Connections
Increase max connections or reduce pool size:
```bash
# Check current connections
fly postgres connect -a linkforty-db -c "SELECT count(*) FROM pg_stat_activity;"
# Check max_connections
fly postgres connect -a linkforty-db -c "SHOW max_connections;"
```
### Slow Queries
```bash
# Enable query logging
fly postgres connect -a linkforty-db
ALTER DATABASE linkforty SET log_min_duration_statement = 1000;
# Analyze slow queries
EXPLAIN ANALYZE SELECT * FROM links WHERE code = 'abc123';
```
### Storage Full
```bash
# Check current usage
fly volumes list -a linkforty-db
# Extend volume
fly volumes extend <volume-id> --size 40 -a linkforty-db
```
## Alternative: External PostgreSQL
Instead of Fly Postgres, you can use:
### Supabase
```bash
# Create project at https://supabase.com
# Get connection string from project settings
fly secrets set DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.xxxxx.supabase.co:5432/postgres?sslmode=require"
```
### Neon
```bash
# Create project at https://neon.tech
# Copy connection string
fly secrets set DATABASE_URL="postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require"
```
### AWS RDS
```bash
# Create RDS PostgreSQL instance
# Ensure security group allows Fly.io IPs
fly secrets set DATABASE_URL="postgresql://admin:password@mydb.xxxxx.us-east-1.rds.amazonaws.com:5432/linkforty?sslmode=require"
```
## Cost Optimization
### Free Tier Setup
- 1x shared-cpu-1x (256MB)
- 10GB storage
- 1 region
- **Cost: $0/month**
### Small Production (~$7/month)
- 1x shared-cpu-2x (512MB)
- 20GB storage
- Daily backups
- **Cost: ~$7/month**
### High Availability (~$20/month)
- 3x shared-cpu-2x (512MB each)
- 20GB storage per instance
- Auto-failover
- **Cost: ~$20/month**
Check pricing:
```bash
fly pricing postgres
```
## Resources
- [Fly.io Postgres Docs](https://fly.io/docs/postgres/)
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
- [Connection Pooling Best Practices](https://www.postgresql.org/docs/current/runtime-config-connection.html)
---
**Need Help?**
- Fly.io Community: https://community.fly.io/
- PostgreSQL Help: https://www.postgresql.org/support/
+464
View File
@@ -0,0 +1,464 @@
# Redis Setup on Fly.io
This guide covers setting up and managing Redis for LinkForty on Fly.io.
## Overview
Redis is **optional but highly recommended** for LinkForty. According to the project documentation, Redis caching can reduce database queries by ~90% for repeated link lookups.
Fly.io partners with [Upstash](https://upstash.com/) to provide managed Redis with:
- Serverless pricing (pay per request)
- Global replication
- TLS encryption
- Automatic scaling
- No idle charges
## Creating a Redis Instance
### Option 1: Upstash Redis via Fly.io (Recommended)
```bash
fly redis create --name linkforty-redis --region iad
```
You'll be prompted to choose a plan:
**Free Tier:**
- 256MB storage
- 10,000 commands/day
- Perfect for development
- **Cost: $0/month**
**Eviction-$10:**
- 1GB storage
- 100K commands/day
- Evicts least-recently-used keys when full
- **Cost: $10/month**
**Eviction-$40:**
- 5GB storage
- 500K commands/day
- Evicts least-recently-used keys when full
- **Cost: $40/month**
**No-Eviction-$120:**
- 5GB storage
- 1M commands/day
- Never evicts keys (blocks writes when full)
- **Cost: $120/month**
### Option 2: Direct Upstash Setup
Alternatively, create directly at [Upstash](https://upstash.com/):
1. Sign up at https://upstash.com
2. Create a new Redis database
3. Choose region (match your Fly.io region)
4. Select pricing plan
5. Copy the connection string
## Setting Redis URL
After creation, you'll receive a `REDIS_URL`. Set it as a secret:
```bash
fly secrets set REDIS_URL="rediss://default:password@host:6379" --app your-linkforty-app
```
**Important:** Use `rediss://` (with double 's') for TLS encryption.
## Redis URL Format
Your `REDIS_URL` should follow this format:
```
rediss://default:password@host:6379
```
**Components:**
- `rediss://` - Redis with TLS (**required** for security)
- `default` - Username (Upstash default)
- `password` - Authentication token from Upstash
- `host` - Upstash endpoint (e.g., `xxx-us-east-1.upstash.io`)
- `6379` - Standard Redis port
## Verifying Redis Connection
After setting the `REDIS_URL`, deploy and check logs:
```bash
fly deploy
fly logs
```
Look for:
```
Redis connected successfully
```
If Redis is unavailable, LinkForty will still work but will query the database for every link lookup (slower).
## How LinkForty Uses Redis
### Cache Strategy
LinkForty caches link lookups with this pattern:
1. **Request:** User visits `https://your-app.fly.dev/abc123`
2. **Check Redis:** Look for cached link data
- **Cache hit:** Return immediately (< 5ms)
- **Cache miss:** Query database, cache result
3. **TTL:** Cached for 1 hour (configurable)
### What Gets Cached
- Link metadata (URLs, targeting rules, UTM params)
- Link expiration status
- User ID associations
### What Doesn't Get Cached
- Click events (always written to database)
- Analytics aggregations
- Link creation/updates
## Performance Benefits
With Redis enabled:
- **Link redirects:** ~5ms (vs ~50-200ms without cache)
- **Database load:** Reduced by ~90% for popular links
- **Scalability:** Handle 1000+ req/s on small instances
Without Redis:
- Every redirect hits the database
- Higher latency for users
- More database resources needed
- Lower maximum throughput
## Monitoring Redis
### Check Connection
```bash
# View app logs
fly logs --app your-linkforty-app | grep -i redis
# Check if REDIS_URL is set
fly secrets list --app your-linkforty-app
```
### Monitor Usage (Upstash Dashboard)
Visit [Upstash Console](https://console.upstash.com/):
- View daily command count
- Monitor memory usage
- Check hit/miss ratio
- See latency metrics
### Redis CLI Access
Connect to your Redis instance:
```bash
# Install redis-cli if needed
brew install redis # macOS
apt-get install redis-tools # Linux
# Connect using REDIS_URL
redis-cli -u "rediss://default:password@host:6379"
# Once connected:
PING # Should return PONG
KEYS link:* # List cached links
GET link:abc123 # View specific cached link
TTL link:abc123 # Check time-to-live
INFO stats # View Redis stats
DBSIZE # Count of keys
FLUSHDB # ⚠️ Clear all cache (use with caution)
```
## Cache Invalidation
### Automatic Invalidation
LinkForty automatically invalidates cache when:
- Link is updated
- Link is deleted
- Link expires
### Manual Invalidation
If needed, clear cache for a specific link:
```bash
redis-cli -u "rediss://default:password@host:6379" DEL link:abc123
```
Clear all cached links:
```bash
redis-cli -u "rediss://default:password@host:6379" FLUSHDB
```
### Cache TTL Configuration
Default TTL is 1 hour. To adjust, modify the caching layer in LinkForty:
```typescript
// In your Redis cache implementation
const TTL = 60 * 60; // 1 hour in seconds
await redis.set(key, value, 'EX', TTL);
```
Consider:
- **Short TTL (5-15 min):** More accurate, higher DB load
- **Long TTL (1-4 hours):** Lower DB load, potentially stale data
- **No TTL:** Manual invalidation only (not recommended)
## Scaling Redis
### When to Upgrade
Monitor these metrics:
- **Commands/day approaching limit** → Upgrade plan
- **Memory usage > 80%** → Upgrade plan or enable eviction
- **High latency (>10ms)** → Consider global replication
- **Frequent cache misses** → Increase TTL or memory
### Upgrade Plan
```bash
# Via Upstash dashboard
# https://console.upstash.com/ → Select database → Change plan
```
No downtime during plan changes.
### Global Replication (Multi-Region)
For lower latency worldwide, enable global replication:
1. In Upstash dashboard, enable "Read Replicas"
2. Choose replica regions (e.g., `us-east`, `eu-west`, `ap-southeast`)
3. Upstash automatically routes reads to nearest replica
**Cost:** ~2x base price per replica region
## Security
### TLS Encryption
Always use `rediss://` (TLS) in production:
```bash
# ✅ Correct (encrypted)
rediss://default:password@host:6379
# ❌ Wrong (unencrypted)
redis://default:password@host:6379
```
### Password Rotation
Rotate Redis password quarterly:
1. In Upstash dashboard: **Database → Settings → Reset Password**
2. Copy new password
3. Update secret:
```bash
fly secrets set REDIS_URL="rediss://default:NEW_PASSWORD@host:6379"
```
4. Deploy:
```bash
fly deploy
```
### Network Security
Upstash Redis is publicly accessible but:
- Requires authentication (password)
- Uses TLS encryption
- Supports IP allowlisting (in Upstash dashboard)
For extra security, enable IP allowlisting:
1. Get your Fly.io app's public IPs: `fly ips list`
2. In Upstash dashboard: **Settings → IP Allowlist**
3. Add Fly.io IPs
## Troubleshooting
### Redis Connection Fails
```bash
# Check REDIS_URL is set correctly
fly secrets list --app your-linkforty-app
# Test connection manually
redis-cli -u "rediss://default:password@host:6379" PING
# Check app logs for errors
fly logs --app your-linkforty-app | grep -i redis
```
Common issues:
- Wrong password → Reset in Upstash dashboard
- Missing `rediss://` protocol → Use TLS version
- Firewall blocking port 6379 → Check Upstash IP allowlist
### High Latency
```bash
# Test latency
redis-cli -u "rediss://default:password@host:6379" --latency
# Check from your app
redis-cli -u "rediss://default:password@host:6379" SLOWLOG GET 10
```
Solutions:
- Enable global replication for multi-region apps
- Check Upstash status page
- Verify region matches your app region
### Memory Full (Eviction Errors)
```bash
# Check memory usage
redis-cli -u "rediss://default:password@host:6379" INFO memory
# Check eviction stats
redis-cli -u "rediss://default:password@host:6379" INFO stats | grep evicted
```
Solutions:
- Upgrade to larger plan
- Reduce cache TTL
- Implement selective caching (only cache popular links)
### Cache Hit Rate Too Low
```bash
# Check hit rate
redis-cli -u "rediss://default:password@host:6379" INFO stats
# Look for: keyspace_hits and keyspace_misses
```
Low hit rate causes:
- TTL too short
- Traffic pattern (mostly unique links)
- Not enough memory (evictions)
Solutions:
- Increase TTL (if data staleness is acceptable)
- Increase memory (upgrade plan)
- Profile which links are being accessed
## Running Without Redis
LinkForty works without Redis, but with performance trade-offs:
### Performance Without Redis
- **Latency:** ~50-200ms per redirect (vs ~5ms with Redis)
- **Database load:** 10x higher
- **Throughput:** Lower maximum requests/second
- **Costs:** Higher database instance needed
### Disabling Redis
Simply don't set `REDIS_URL`:
```bash
fly secrets unset REDIS_URL --app your-linkforty-app
```
LinkForty detects missing Redis and falls back to database-only mode.
### When to Skip Redis
Skip Redis if:
- Very low traffic (< 100 redirects/day)
- Budget is extremely tight
- All links are unique (no repeated lookups)
- Development/testing only
## Cost Optimization
### Free Tier (Good for Development)
- 256MB storage
- 10,000 commands/day
- ~300 redirects/day with caching
- **Cost: $0/month**
### Small Production ($10/month)
- 1GB storage
- 100K commands/day
- ~3,000 redirects/day with caching
- **Cost: $10/month**
### Estimate Your Needs
Calculate commands/day:
- Link redirect with cache hit: 1 command (`GET`)
- Link redirect with cache miss: 2 commands (`GET` + `SET`)
- Assume 50% hit rate: 1.5 commands per redirect
- For 10,000 redirects/day: ~15,000 commands/day
Choose plan accordingly.
### Cost Monitoring
```bash
# In Upstash dashboard
# View: Billing → Current Usage
```
Set up alerts for:
- 80% of command limit reached
- 80% of storage used
## Alternative: Self-Hosted Redis
Instead of Upstash, you can run Redis on Fly.io:
### Create Redis Machine
```bash
fly launch --image redis:7-alpine --name linkforty-redis --region iad
```
**Pros:**
- Lower cost for high traffic
- Full control
**Cons:**
- You manage backups
- You manage security
- No automatic scaling
- More operational overhead
**Not recommended** unless you have specific requirements or very high traffic (>1M commands/day).
## Best Practices
1. **Always use TLS** (`rediss://`)
2. **Set reasonable TTL** (1-4 hours for links)
3. **Monitor hit rate** (target >70%)
4. **Rotate passwords** quarterly
5. **Enable global replication** for multi-region deployments
6. **Set up alerts** for command limits
7. **Start small** (free tier) and scale up based on metrics
## Resources
- [Upstash Documentation](https://docs.upstash.com/redis)
- [Redis Documentation](https://redis.io/documentation)
- [Fly.io Redis Guide](https://fly.io/docs/reference/redis/)
- [Cache Invalidation Strategies](https://redis.io/docs/manual/keyspace-notifications/)
---
**Need Help?**
- Upstash Support: https://upstash.com/docs/redis/support
- Redis Community: https://redis.io/community
- Fly.io Community: https://community.fly.io/
+71
View File
@@ -0,0 +1,71 @@
# Fly.io configuration for LinkForty
# See https://fly.io/docs/reference/configuration/ for full documentation
app = "linkforty-core" # Change this to your unique app name
primary_region = "iad" # Change to your preferred region (iad = Washington DC)
# Build configuration
[build]
# Use the Dockerfile from the examples directory
dockerfile = "../../examples/Dockerfile"
# Environment variables (non-sensitive)
[env]
NODE_ENV = "production"
PORT = "8080"
# DATABASE_URL and REDIS_URL should be set as secrets (see DEPLOYMENT.md)
# HTTP service configuration
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = false # Keep at least one machine always running
auto_start_machines = true
min_machines_running = 1 # Always keep at least one instance running
# HTTP service concurrency
[http_service.concurrency]
type = "requests"
hard_limit = 250 # Maximum concurrent requests per machine
soft_limit = 200 # Target concurrent requests before scaling
# Health checks
[[http_service.checks]]
grace_period = "10s" # Wait before starting health checks
interval = "30s" # Check every 30 seconds
method = "GET"
timeout = "5s"
path = "/health" # Ensure your app has a /health endpoint
# VM resources
# Start with shared-cpu-1x (256MB RAM) - scale up as needed
[vm]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
# Metrics (optional but recommended)
[[metrics]]
port = 9091 # If you add Prometheus metrics
path = "/metrics"
# Deploy configuration
[deploy]
release_command = "npm run migrate" # Run migrations before deploying
strategy = "rolling" # Rolling deployment strategy
# Scaling configuration
# Uncomment and adjust for auto-scaling based on traffic
# [[services.autoscaling]]
# enabled = true
# min_count = 1 # Minimum number of machines
# max_count = 10 # Maximum number of machines
# Multi-region deployment (optional)
# Uncomment to deploy to multiple regions for lower latency
# [[regions]]
# name = "iad" # Primary region (US East)
# [[regions]]
# name = "lhr" # Europe (London)
# [[regions]]
# name = "syd" # Asia-Pacific (Sydney)