infra
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1008 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,45 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install curl and dumb-init for health checks and signal handling
|
||||
# Split to avoid busybox trigger issues in ARM64 QEMU builds
|
||||
RUN apk add --no-cache --no-scripts curl dumb-init && \
|
||||
/bin/busybox --install -s || true
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1001 -S linkforty && \
|
||||
adduser -S linkforty -u 1001 -G linkforty
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies (skip prepare script since we already built in CI)
|
||||
RUN npm ci --only=production --ignore-scripts && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy source files
|
||||
COPY dist ./dist
|
||||
COPY examples/basic-server.ts ./
|
||||
|
||||
# Install tsx for running TypeScript
|
||||
RUN npm install -g tsx
|
||||
|
||||
# Change ownership to non-root user
|
||||
RUN chown -R linkforty:linkforty /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER linkforty
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/sdk/v1/health || exit 1
|
||||
|
||||
# Use dumb-init for proper signal handling
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
# Run migrations on startup and then start server
|
||||
CMD ["sh", "-c", "tsx dist/scripts/migrate.js && tsx basic-server.ts"]
|
||||
@@ -0,0 +1,329 @@
|
||||
<div align="center">
|
||||
<img src="../assets/logo.png" alt="LinkForty Logo" width="150"/>
|
||||
|
||||
# @linkforty/core Examples
|
||||
|
||||
This directory contains example implementations for deploying LinkForty Core.
|
||||
</div>
|
||||
|
||||
## Quick Start with Docker Compose
|
||||
|
||||
The easiest way to get started is using Docker Compose, which will set up PostgreSQL, Redis, and the LinkForty server.
|
||||
|
||||
### 1. Start All Services
|
||||
|
||||
```bash
|
||||
cd examples
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will start:
|
||||
- PostgreSQL database (port 5432)
|
||||
- Redis cache (port 6379)
|
||||
- LinkForty server (port 3000)
|
||||
|
||||
### 2. Access the Server
|
||||
|
||||
The API will be available at `http://localhost:3000`
|
||||
|
||||
**Test it:**
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Create a test link (you'll need a userId first)
|
||||
curl -X POST http://localhost:3000/api/links \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "test-user",
|
||||
"originalUrl": "https://example.com",
|
||||
"title": "My First Link"
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. View Logs
|
||||
|
||||
```bash
|
||||
docker compose logs -f linkforty
|
||||
```
|
||||
|
||||
### 4. Stop Services
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
To remove volumes (data will be lost):
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## Basic Server (Node.js)
|
||||
|
||||
If you prefer to run the server directly with Node.js:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- PostgreSQL 14+
|
||||
- Redis 6+
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @linkforty/core
|
||||
```
|
||||
|
||||
### 2. Start PostgreSQL and Redis
|
||||
|
||||
Using Docker:
|
||||
```bash
|
||||
docker run -d --name postgres -p 5432:5432 \
|
||||
-e POSTGRES_DB=linkforty \
|
||||
-e POSTGRES_USER=linkforty \
|
||||
-e POSTGRES_PASSWORD=changeme \
|
||||
postgres:15-alpine
|
||||
|
||||
docker run -d --name redis -p 6379:6379 \
|
||||
redis:7-alpine
|
||||
```
|
||||
|
||||
Or install locally using your package manager.
|
||||
|
||||
### 3. Run the Example Server
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export DATABASE_URL=postgresql://linkforty:changeme@localhost:5432/linkforty
|
||||
export REDIS_URL=redis://localhost:6379
|
||||
export PORT=3000
|
||||
|
||||
# Run the server
|
||||
npx tsx examples/basic-server.ts
|
||||
```
|
||||
|
||||
## Custom Implementation
|
||||
|
||||
You can also create your own server implementation:
|
||||
|
||||
### TypeScript Example
|
||||
|
||||
```typescript
|
||||
import { createServer } from '@linkforty/core';
|
||||
|
||||
async function start() {
|
||||
const server = await createServer({
|
||||
database: {
|
||||
url: process.env.DATABASE_URL,
|
||||
pool: {
|
||||
min: 2,
|
||||
max: 10,
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
url: process.env.REDIS_URL,
|
||||
},
|
||||
cors: {
|
||||
origin: ['https://yourdomain.com'],
|
||||
},
|
||||
logger: true,
|
||||
});
|
||||
|
||||
// Add custom routes
|
||||
server.get('/custom', async (request, reply) => {
|
||||
return { message: 'Custom endpoint' };
|
||||
});
|
||||
|
||||
await server.listen({
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
});
|
||||
|
||||
console.log('Server running!');
|
||||
}
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
### JavaScript Example
|
||||
|
||||
```javascript
|
||||
const { createServer } = require('@linkforty/core');
|
||||
|
||||
async function start() {
|
||||
const server = await createServer({
|
||||
database: {
|
||||
url: 'postgresql://linkforty:changeme@localhost:5432/linkforty'
|
||||
},
|
||||
redis: {
|
||||
url: 'redis://localhost:6379'
|
||||
}
|
||||
});
|
||||
|
||||
await server.listen({ port: 3000, host: '0.0.0.0' });
|
||||
console.log('Server running on http://localhost:3000');
|
||||
}
|
||||
|
||||
start().catch(console.error);
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://linkforty:changeme@localhost:5432/linkforty
|
||||
REDIS_URL=redis://localhost:6379
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
CORS_ORIGIN=*
|
||||
```
|
||||
|
||||
## Database Migrations
|
||||
|
||||
The database schema is automatically initialized on first startup. If you need to run migrations manually:
|
||||
|
||||
```bash
|
||||
npx tsx node_modules/@linkforty/core/dist/scripts/migrate.js
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
For production deployments:
|
||||
|
||||
1. **Use environment variables** for configuration
|
||||
2. **Enable Redis** for caching
|
||||
3. **Set up PostgreSQL replication** for high availability
|
||||
4. **Use a process manager** (PM2, systemd)
|
||||
5. **Set NODE_ENV=production**
|
||||
6. **Configure CORS** to allow only your domains
|
||||
7. **Use HTTPS** with a reverse proxy (nginx, Caddy)
|
||||
|
||||
### Example with PM2
|
||||
|
||||
```bash
|
||||
# Install PM2
|
||||
npm install -g pm2
|
||||
|
||||
# Start server
|
||||
pm2 start examples/basic-server.ts --name linkforty
|
||||
|
||||
# View logs
|
||||
pm2 logs linkforty
|
||||
|
||||
# Monitor
|
||||
pm2 monit
|
||||
|
||||
# Restart
|
||||
pm2 restart linkforty
|
||||
|
||||
# Set to start on boot
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### Example nginx config
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name links.yourdomain.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Usage Examples
|
||||
|
||||
### Create a Link
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/links \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "user-123",
|
||||
"originalUrl": "https://example.com/product",
|
||||
"title": "Product Page",
|
||||
"iosUrl": "myapp://product/123",
|
||||
"androidUrl": "myapp://product/123",
|
||||
"utmParameters": {
|
||||
"source": "twitter",
|
||||
"medium": "social",
|
||||
"campaign": "launch"
|
||||
},
|
||||
"customCode": "product-launch"
|
||||
}'
|
||||
```
|
||||
|
||||
### Get All Links
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3000/api/links?userId=user-123"
|
||||
```
|
||||
|
||||
### Update a Link
|
||||
|
||||
```bash
|
||||
curl -X PUT "http://localhost:3000/api/links/link-id?userId=user-123" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Updated Title",
|
||||
"isActive": false
|
||||
}'
|
||||
```
|
||||
|
||||
### Get Analytics
|
||||
|
||||
```bash
|
||||
# Overview
|
||||
curl "http://localhost:3000/api/analytics/overview?userId=user-123&days=30"
|
||||
|
||||
# Link-specific
|
||||
curl "http://localhost:3000/api/analytics/links/link-id?userId=user-123&days=7"
|
||||
```
|
||||
|
||||
### Test Redirect
|
||||
|
||||
```bash
|
||||
curl -I http://localhost:3000/product-launch
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
- Verify PostgreSQL is running: `docker ps | grep postgres`
|
||||
- Check connection string in DATABASE_URL
|
||||
- Ensure database exists: `psql -U linkforty -d linkforty -c "SELECT 1;"`
|
||||
|
||||
### Redis Connection Failed
|
||||
|
||||
- Verify Redis is running: `docker ps | grep redis`
|
||||
- Test connection: `redis-cli ping`
|
||||
- Make sure REDIS_URL is correct
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
- Check what's using the port: `lsof -i :3000`
|
||||
- Change the PORT environment variable
|
||||
|
||||
### Migrations Not Running
|
||||
|
||||
- Run manually: `npx tsx node_modules/@linkforty/core/dist/scripts/migrate.js`
|
||||
- Check database permissions
|
||||
|
||||
## Support
|
||||
|
||||
- Documentation: https://github.com/linkforty/core
|
||||
- Issues: https://github.com/linkforty/core/issues
|
||||
- Discussions: https://github.com/linkforty/core/discussions
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createServer } from '@linkforty/core';
|
||||
|
||||
function getTrustProxy(): boolean | number | undefined {
|
||||
const v = process.env.TRUST_PROXY;
|
||||
if (v === undefined || v === '') return undefined;
|
||||
if (v === '1' || v.toLowerCase() === 'true') return true;
|
||||
const n = Number(v);
|
||||
if (!Number.isNaN(n) && n >= 0) return n;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function start() {
|
||||
const server = await createServer({
|
||||
database: {
|
||||
url: process.env.DATABASE_URL || 'postgresql://postgres:password@localhost:5432/linkforty',
|
||||
},
|
||||
redis: {
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||
},
|
||||
cors: {
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
},
|
||||
trustProxy: getTrustProxy(),
|
||||
});
|
||||
|
||||
await server.listen({
|
||||
port: Number(process.env.PORT) || 3000,
|
||||
host: '0.0.0.0',
|
||||
});
|
||||
|
||||
console.log('LinkForty server running on http://localhost:3000');
|
||||
console.log('');
|
||||
console.log('API Endpoints:');
|
||||
console.log(' POST /api/links - Create a new link');
|
||||
console.log(' GET /api/links - List all links (requires ?userId=xxx)');
|
||||
console.log(' GET /api/links/:id - Get a specific link (requires ?userId=xxx)');
|
||||
console.log(' PUT /api/links/:id - Update a link (requires ?userId=xxx)');
|
||||
console.log(' DELETE /api/links/:id - Delete a link (requires ?userId=xxx)');
|
||||
console.log(' GET /api/analytics/overview - Get analytics overview (requires ?userId=xxx)');
|
||||
console.log(' GET /api/analytics/links/:linkId - Get link analytics (requires ?userId=xxx)');
|
||||
console.log('');
|
||||
console.log('Public Endpoint:');
|
||||
console.log(' GET /:shortCode - Redirect to target URL');
|
||||
}
|
||||
|
||||
start().catch(console.error);
|
||||
@@ -0,0 +1,56 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: linkforty
|
||||
POSTGRES_USER: linkforty
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U linkforty"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
linkforty:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: examples/Dockerfile
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgresql://linkforty:changeme@postgres:5432/linkforty?sslmode=disable
|
||||
REDIS_URL: redis://redis:6379
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
ports:
|
||||
- "3000:3000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/sdk/v1/health"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
@@ -0,0 +1,425 @@
|
||||
<div align="center">
|
||||
<img src="../assets/logo.png" alt="LinkForty Logo" width="150"/>
|
||||
|
||||
# Contributing Infrastructure Templates
|
||||
|
||||
Thank you for your interest in contributing infrastructure templates to LinkForty! This guide will help you add support for new cloud platforms and deployment methods.
|
||||
</div>
|
||||
|
||||
## Overview
|
||||
|
||||
We welcome community contributions for additional infrastructure providers. This allows LinkForty users to deploy on their preferred platforms with minimal setup.
|
||||
|
||||
## Official vs Community Support
|
||||
|
||||
### Official Support (Maintained by Core Team)
|
||||
- Docker & Docker Compose (`examples/`)
|
||||
- Fly.io (`infra/fly.io/`)
|
||||
|
||||
### Community Support (Maintained by Contributors)
|
||||
- All other platforms
|
||||
- We validate they work but may not provide detailed troubleshooting
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
### Before You Start
|
||||
|
||||
1. **Check existing issues/PRs** to avoid duplicate work
|
||||
2. **Open a discussion** on GitHub to gauge interest
|
||||
3. **Choose a popular platform** that benefits many users
|
||||
4. **Test thoroughly** before submitting
|
||||
|
||||
### Requested Providers
|
||||
|
||||
We're especially interested in templates for:
|
||||
- **AWS** (ECS/Fargate, Elastic Beanstalk)
|
||||
- **Google Cloud** (Cloud Run, GKE)
|
||||
- **Azure** (Container Instances, App Service)
|
||||
- **Railway**
|
||||
- **Render**
|
||||
- **DigitalOcean App Platform**
|
||||
- **Kubernetes** (generic manifests)
|
||||
- **Terraform** (multi-cloud IaC)
|
||||
|
||||
## Template Requirements
|
||||
|
||||
### Must-Have Components
|
||||
|
||||
Your infrastructure template **must** include:
|
||||
|
||||
1. **Configuration Files**
|
||||
- Platform-specific deployment config (e.g., `app.yaml`, `terraform.tf`)
|
||||
- Environment variable template
|
||||
- Build/deployment instructions
|
||||
|
||||
2. **Documentation**
|
||||
- `DEPLOYMENT.md` - Step-by-step deployment guide
|
||||
- Database setup instructions
|
||||
- Redis setup instructions (optional but recommended)
|
||||
- Cost estimates for different tiers
|
||||
|
||||
3. **Security Guidelines**
|
||||
- SSL/TLS configuration
|
||||
- Secret management best practices
|
||||
- Network security recommendations
|
||||
- Backup strategy
|
||||
|
||||
4. **Testing**
|
||||
- Proof of successful deployment
|
||||
- Screenshots or deployment logs
|
||||
- Performance benchmarks (optional)
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Add your template under `infra/[provider]/`:
|
||||
|
||||
```
|
||||
infra/
|
||||
├── your-provider/
|
||||
│ ├── README.md # Quick overview
|
||||
│ ├── DEPLOYMENT.md # Detailed deployment guide
|
||||
│ ├── config.[ext] # Platform-specific config
|
||||
│ ├── .env.production.example # Environment variables
|
||||
│ └── [additional files] # Provider-specific resources
|
||||
```
|
||||
|
||||
### Example Structure: AWS ECS
|
||||
|
||||
```
|
||||
infra/
|
||||
├── aws-ecs/
|
||||
│ ├── README.md # Overview of AWS ECS deployment
|
||||
│ ├── DEPLOYMENT.md # Step-by-step guide
|
||||
│ ├── task-definition.json # ECS task definition
|
||||
│ ├── service.json # ECS service config
|
||||
│ ├── cloudformation.yaml # Infrastructure template (optional)
|
||||
│ ├── .env.production.example # Environment variables
|
||||
│ └── scripts/
|
||||
│ ├── setup.sh # Automated setup script (optional)
|
||||
│ └── deploy.sh # Deployment script (optional)
|
||||
```
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### DEPLOYMENT.md Template
|
||||
|
||||
Your `DEPLOYMENT.md` should include:
|
||||
|
||||
```markdown
|
||||
# Deploying LinkForty to [Provider Name]
|
||||
|
||||
## Prerequisites
|
||||
- Account setup
|
||||
- CLI tools needed
|
||||
- Required permissions
|
||||
|
||||
## Step 1: Database Setup
|
||||
- PostgreSQL 13+ configuration
|
||||
- Connection string format
|
||||
- Security settings
|
||||
|
||||
## Step 2: Redis Setup (Optional)
|
||||
- Redis configuration
|
||||
- Connection string format
|
||||
|
||||
## Step 3: Application Deployment
|
||||
- Configuration steps
|
||||
- Secret management
|
||||
- Build and deploy process
|
||||
|
||||
## Step 4: Verification
|
||||
- Health check verification
|
||||
- Test redirect creation
|
||||
- Troubleshooting common issues
|
||||
|
||||
## Scaling
|
||||
- Vertical scaling
|
||||
- Horizontal scaling
|
||||
- Multi-region deployment
|
||||
|
||||
## Monitoring
|
||||
- Logs access
|
||||
- Metrics dashboard
|
||||
- Alerts setup
|
||||
|
||||
## Cost Estimation
|
||||
- Free tier (if available)
|
||||
- Small production setup
|
||||
- Large production setup
|
||||
|
||||
## Troubleshooting
|
||||
- Common issues and solutions
|
||||
|
||||
## Resources
|
||||
- Platform documentation links
|
||||
```
|
||||
|
||||
### README.md Template
|
||||
|
||||
```markdown
|
||||
# LinkForty on [Provider Name]
|
||||
|
||||
Quick overview of deploying LinkForty to [Provider].
|
||||
|
||||
## Quick Start
|
||||
|
||||
[One-click deploy button if available]
|
||||
|
||||
## Features
|
||||
- What makes this platform good for LinkForty
|
||||
- Key benefits
|
||||
|
||||
## Cost Estimate
|
||||
- Starting at $X/month
|
||||
|
||||
## Documentation
|
||||
- [Full deployment guide](./DEPLOYMENT.md)
|
||||
|
||||
## Support
|
||||
- Community-maintained
|
||||
- Link to platform's support
|
||||
```
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### Configuration Files
|
||||
|
||||
- **Use comments** to explain non-obvious settings
|
||||
- **Include defaults** that work for most users
|
||||
- **Parameterize** where possible (don't hardcode values)
|
||||
- **Follow platform conventions** (naming, structure)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- **Match LinkForty's standards** (see `examples/.env.example`)
|
||||
- **Include all required variables**
|
||||
- **Document optional variables**
|
||||
- **Show example values** (with placeholders for secrets)
|
||||
|
||||
### Security
|
||||
|
||||
- **Never include actual secrets** in example files
|
||||
- **Use secure defaults** (SSL/TLS, authentication)
|
||||
- **Document security best practices**
|
||||
- **Include secret rotation instructions**
|
||||
|
||||
## Testing Your Template
|
||||
|
||||
Before submitting, test your template:
|
||||
|
||||
### 1. Fresh Deployment Test
|
||||
|
||||
- [ ] Start from scratch (new account or clean environment)
|
||||
- [ ] Follow your DEPLOYMENT.md step-by-step
|
||||
- [ ] Verify all commands work as documented
|
||||
- [ ] Note any issues or unclear steps
|
||||
|
||||
### 2. Functional Test
|
||||
|
||||
- [ ] Application starts successfully
|
||||
- [ ] Health check endpoint responds
|
||||
- [ ] Create a test link via API
|
||||
- [ ] Verify redirect works
|
||||
- [ ] Check logs are accessible
|
||||
- [ ] Test database connection
|
||||
- [ ] Test Redis connection (if used)
|
||||
|
||||
### 3. Security Test
|
||||
|
||||
- [ ] HTTPS is enforced
|
||||
- [ ] Secrets are not exposed in logs
|
||||
- [ ] Database uses SSL
|
||||
- [ ] CORS is configurable
|
||||
- [ ] No public exposure of internal services
|
||||
|
||||
### 4. Documentation Test
|
||||
|
||||
- [ ] Another person can follow your guide successfully
|
||||
- [ ] All prerequisites are listed
|
||||
- [ ] Cost estimates are accurate
|
||||
- [ ] Troubleshooting section is helpful
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### 1. Prepare Your PR
|
||||
|
||||
Create a branch:
|
||||
```bash
|
||||
git checkout -b infra/add-[provider-name]
|
||||
```
|
||||
|
||||
Add your files:
|
||||
```bash
|
||||
git add infra/[provider-name]/
|
||||
```
|
||||
|
||||
Commit with clear message:
|
||||
```bash
|
||||
git commit -m "Add infrastructure template for [Provider Name]"
|
||||
```
|
||||
|
||||
### 2. Update Main README
|
||||
|
||||
Add your provider to `infra/README.md`:
|
||||
|
||||
```markdown
|
||||
### [Provider Name] (Community)
|
||||
**Location:** [`infra/[provider-name]/`](./[provider-name]/)
|
||||
|
||||
**Best for:**
|
||||
- [Use case 1]
|
||||
- [Use case 2]
|
||||
|
||||
[View deployment guide →](./[provider-name]/DEPLOYMENT.md)
|
||||
```
|
||||
|
||||
### 3. PR Description Template
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
Adds infrastructure template for deploying LinkForty to [Provider Name].
|
||||
|
||||
## What's Included
|
||||
- [ ] Configuration files
|
||||
- [ ] Deployment guide
|
||||
- [ ] Environment variable template
|
||||
- [ ] Security documentation
|
||||
- [ ] Cost estimates
|
||||
|
||||
## Testing
|
||||
- [ ] Successfully deployed to [Provider]
|
||||
- [ ] Verified all features work
|
||||
- [ ] Tested by at least one other person
|
||||
|
||||
## Screenshots/Logs
|
||||
[Include proof of successful deployment]
|
||||
|
||||
## Cost Estimate
|
||||
- Free tier: [Yes/No]
|
||||
- Small production: ~$X/month
|
||||
- Medium production: ~$Y/month
|
||||
|
||||
## Additional Notes
|
||||
[Any platform-specific quirks or considerations]
|
||||
```
|
||||
|
||||
### 4. Review Process
|
||||
|
||||
Your PR will be reviewed for:
|
||||
- **Completeness** - All required components present
|
||||
- **Accuracy** - Instructions work as documented
|
||||
- **Security** - Follows best practices
|
||||
- **Quality** - Clear, well-documented
|
||||
- **Maintenance** - Reasonable to maintain
|
||||
|
||||
We may request changes or improvements.
|
||||
|
||||
### 5. After Merge
|
||||
|
||||
- Your template will be listed in the main README
|
||||
- You'll be credited as the maintainer
|
||||
- Users may open issues specific to your platform
|
||||
- You'll be tagged for platform-specific questions
|
||||
|
||||
## Maintenance Expectations
|
||||
|
||||
### As a Contributor
|
||||
|
||||
You're expected to:
|
||||
- **Respond to issues** related to your platform (within reason)
|
||||
- **Update templates** when platform changes significantly
|
||||
- **Test updates** before merging changes
|
||||
- **Notify maintainers** if you can no longer maintain
|
||||
|
||||
### If You Can't Maintain
|
||||
|
||||
If you can't continue maintaining:
|
||||
1. Open an issue titled "Seeking maintainer for [Provider]"
|
||||
2. Tag current maintainers
|
||||
3. We'll mark it as "community-seeking-maintainer"
|
||||
|
||||
### What Core Maintainers Will Do
|
||||
|
||||
- Review and merge PRs for your platform
|
||||
- Help with general LinkForty questions
|
||||
- Validate deployments still work (periodically)
|
||||
- Archive unmaintained platforms (if necessary)
|
||||
|
||||
### What Core Maintainers Won't Do
|
||||
|
||||
- Debug provider-specific issues (beyond basic validation)
|
||||
- Maintain deep expertise in all platforms
|
||||
- Provide 24/7 support for community platforms
|
||||
|
||||
## Support Boundaries
|
||||
|
||||
### In-Scope Support (You Provide)
|
||||
|
||||
- Platform-specific deployment questions
|
||||
- Configuration issues unique to the platform
|
||||
- Cost optimization for that platform
|
||||
- Platform CLI/API usage
|
||||
|
||||
### Out-of-Scope Support (User's Responsibility)
|
||||
|
||||
- Cloud account setup and billing
|
||||
- Platform account permissions/access issues
|
||||
- General cloud computing questions
|
||||
- Non-Link-Forty application issues
|
||||
|
||||
### Core LinkForty Support (Maintainers Provide)
|
||||
|
||||
- Application bugs
|
||||
- Database schema issues
|
||||
- API behavior questions
|
||||
- General architecture questions
|
||||
|
||||
## Examples to Learn From
|
||||
|
||||
### Good Example: Fly.io Template
|
||||
|
||||
Located at `infra/fly.io/`, this template demonstrates:
|
||||
- ✅ Clear, step-by-step instructions
|
||||
- ✅ Complete configuration files with comments
|
||||
- ✅ Security checklist
|
||||
- ✅ Cost estimates at multiple tiers
|
||||
- ✅ Troubleshooting section
|
||||
- ✅ Links to platform documentation
|
||||
|
||||
### What Not to Do
|
||||
|
||||
- ❌ One-liner deployment without explanation
|
||||
- ❌ Missing environment variables
|
||||
- ❌ No security guidance
|
||||
- ❌ Untested instructions
|
||||
- ❌ Hardcoded values (like app names)
|
||||
- ❌ No cost information
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Questions About Contributing
|
||||
|
||||
- Open a discussion on GitHub
|
||||
- Tag maintainers: @[maintainer-username]
|
||||
- Join our community chat (if available)
|
||||
|
||||
### Technical Questions
|
||||
|
||||
- Review existing templates (especially `infra/fly.io/`)
|
||||
- Check LinkForty's main documentation
|
||||
- Ask in GitHub Discussions
|
||||
|
||||
## Recognition
|
||||
|
||||
Contributors will be:
|
||||
- Listed in the template's README as maintainer
|
||||
- Mentioned in release notes
|
||||
- Credited in the project's contributors list
|
||||
|
||||
Thank you for helping make LinkForty accessible on more platforms!
|
||||
|
||||
## License
|
||||
|
||||
All infrastructure templates are licensed under the same MIT license as LinkForty Core.
|
||||
|
||||
By contributing, you agree to license your contribution under this license.
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<div align="center">
|
||||
<img src="../assets/logo.png" alt="LinkForty Logo" width="150"/>
|
||||
|
||||
# LinkForty Infrastructure
|
||||
|
||||
This directory contains infrastructure-as-code templates and deployment guides for running LinkForty in production environments.
|
||||
</div>
|
||||
|
||||
## Available Deployment Options
|
||||
|
||||
### Docker & Docker Compose (Self-Hosted)
|
||||
For local development or self-managed infrastructure.
|
||||
|
||||
**Location:** [`examples/`](../examples/)
|
||||
|
||||
**Best for:**
|
||||
- Local development
|
||||
- Self-managed VPS/bare metal servers
|
||||
- Custom infrastructure requirements
|
||||
- Full control over all components
|
||||
|
||||
**What's included:**
|
||||
- Multi-stage Dockerfile for production builds
|
||||
- Docker Compose orchestration with PostgreSQL and Redis
|
||||
- Environment configuration examples
|
||||
|
||||
[View Docker deployment guide →](../examples/README.md)
|
||||
|
||||
---
|
||||
|
||||
### Fly.io (Managed Platform)
|
||||
Recommended for production deployments with minimal DevOps overhead.
|
||||
|
||||
**Location:** [`infra/fly.io/`](./fly.io/)
|
||||
|
||||
**Best for:**
|
||||
- Production deployments
|
||||
- Global edge distribution
|
||||
- Auto-scaling applications
|
||||
- Managed PostgreSQL and Redis
|
||||
- Teams without dedicated DevOps
|
||||
|
||||
**What's included:**
|
||||
- `fly.toml` - Application configuration
|
||||
- Deployment guide with step-by-step instructions
|
||||
- PostgreSQL and Redis setup guides
|
||||
- Production security checklist
|
||||
- Environment configuration templates
|
||||
|
||||
[View Fly.io deployment guide →](./fly.io/DEPLOYMENT.md)
|
||||
|
||||
**Estimated costs:** Starting at ~$10-15/month for small production deployments
|
||||
|
||||
---
|
||||
|
||||
## Choosing a Deployment Option
|
||||
|
||||
| Feature | Docker Compose | Fly.io |
|
||||
|-------------------------------|----------------------------|-----------------------------|
|
||||
| **Setup complexity** | Medium | Low |
|
||||
| **Infrastructure management** | You manage | Fully managed |
|
||||
| **Scaling** | Manual | Automatic |
|
||||
| **Global distribution** | Manual setup | Built-in edge regions |
|
||||
| **Database backups** | You configure | Automated |
|
||||
| **SSL/TLS certificates** | You configure | Automatic |
|
||||
| **Cost** | Infrastructure only | ~$10-15+/month |
|
||||
| **Best for** | Custom needs, self-hosting | Production, fast deployment |
|
||||
|
||||
## Community-Contributed Providers
|
||||
|
||||
We welcome community contributions for additional infrastructure providers! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
||||
|
||||
**Requested providers:**
|
||||
- AWS (ECS/Fargate)
|
||||
- Google Cloud Run
|
||||
- Azure Container Instances
|
||||
- Railway
|
||||
- Render
|
||||
- DigitalOcean App Platform
|
||||
|
||||
## Support
|
||||
|
||||
### Official Support
|
||||
We provide official support and maintenance for:
|
||||
- Docker & Docker Compose templates (in `examples/`)
|
||||
- Fly.io templates (in `infra/fly.io/`)
|
||||
|
||||
### Community Support
|
||||
Additional provider templates are community-maintained. We validate they work but may not provide detailed troubleshooting for provider-specific issues.
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Application issues:** [GitHub Issues](https://github.com/yourusername/linkforty-core/issues)
|
||||
- **Infrastructure questions:** Check provider-specific documentation first
|
||||
- **Security concerns:** See [SECURITY.md](../SECURITY.md)
|
||||
|
||||
## Security Notice
|
||||
|
||||
**Production Deployment Responsibility**
|
||||
|
||||
While we provide infrastructure templates and security checklists, **you are responsible for**:
|
||||
- Securing your cloud provider accounts
|
||||
- Managing secrets and API keys
|
||||
- Configuring firewalls and network policies
|
||||
- Compliance with relevant regulations (GDPR, HIPAA, etc.)
|
||||
- Monitoring and incident response
|
||||
- Cost management and billing
|
||||
|
||||
Always review the security checklist for your chosen platform before deploying to production.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Choose your deployment platform
|
||||
2. Follow the deployment guide in the respective directory
|
||||
3. Review the security checklist
|
||||
4. Deploy and test
|
||||
5. Set up monitoring and backups
|
||||
|
||||
## License
|
||||
|
||||
All infrastructure templates are provided under the same MIT license as LinkForty Core.
|
||||
@@ -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
|
||||
#
|
||||
# ============================================================================
|
||||
@@ -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/)
|
||||
@@ -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.
|
||||
@@ -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/
|
||||
@@ -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/
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user