Enterprise Deployment Guide¶
Deploy SkillMeat to production with PostgreSQL, Clerk authentication, monitoring, and TLS security. Designed for teams, self-hosted deployments, and multi-tenant scenarios.
Quick Start¶
For a complete production setup with monitoring:
cp .env.enterprise.example .env
# Edit .env with your configuration (see Configuration section)
./compose.sh --profile enterprise -f docker-compose.yml -f docker-compose.monitoring.yml up -d
Or use the Makefile shortcut:
Then open http://localhost:3000. For TLS and reverse proxy setup, see TLS & Reverse Proxy.
Note: ./compose.sh auto-detects Docker vs Podman, so it works on both Fedora (Podman) and macOS/Ubuntu (Docker).
For AWS infrastructure, use the AWS Deployment Guide. It covers managed staging/production on ALB, ECS Fargate web/API services, private RDS PostgreSQL, Secrets Manager, CloudWatch Logs, and GHCR-published images. It also documents dev-fargate for disposable cloud-parity testing and dev-ec2 for single-instance Compose development, where the enterprise profile starts Postgres on the instance instead of RDS.
Prerequisites¶
- Docker Engine v24 or later
- Docker Compose v2.0 or later
- 8 GB RAM minimum (16 GB recommended)
- 20 GB disk space (includes PostgreSQL and monitoring)
- PostgreSQL 13+ (can use composed Postgres or external instance)
- Clerk account (free tier available at clerk.com)
- Monitoring stack (optional): Prometheus, Grafana, Loki
Architecture¶
Enterprise deployment provides production-ready services with database persistence, authentication, monitoring, and security:
┌─────────────────────────────────────────────────────────────┐
│ Your Infrastructure │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Reverse Proxy (Nginx/Caddy) │ │
│ │ - TLS termination │ │
│ │ - Rate limiting │ │
│ │ - Load balancing (optional) │ │
│ └─────┬────────────────────────────────┬───────────────┘ │
│ │ │ │
│ ┌────▼──────┐ ┌──────────────┐ ┌────▼──────┐ │
│ │ Web │ │ API │ │ Prometheus│ │
│ │ 3000 │ │ 8080 │ │ │ │
│ └────┬──────┘ └──────┬───────┘ └───────────┘ │
│ │ │ │
│ └────────┬───────┘ │
│ │ │
│ ┌────────▼──────────┐ │
│ │ PostgreSQL │ │
│ │ 5432 │ │
│ └───────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Monitoring (optional) │ │
│ │ ├─ Prometheus (metrics) │ │
│ │ ├─ Grafana (dashboards) │ │
│ │ └─ Loki (logs) │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
External Services:
├─ Clerk (authentication)
└─ GitHub API (optional: for artifact marketplace)
Services¶
| Service | Port | Profile | Purpose |
|---|---|---|---|
| API (FastAPI) | 8080 | enterprise | Backend REST API |
| Web (Next.js) | 3000 | enterprise | Frontend web interface |
| PostgreSQL | 5432 | enterprise | Production database |
| Prometheus | 9090 | enterprise + monitoring | Metrics collection |
| Grafana | 3001 | enterprise + monitoring | Metrics dashboards |
| Loki | 3100 | enterprise + monitoring | Log aggregation |
Installation & Setup¶
Step 1: Initialize Configuration¶
Copy the enterprise environment template:
Step 2: Configure Essential Variables¶
Edit .env with your settings:
# Application
SKILLMEAT_ENV=production
SKILLMEAT_EDITION=enterprise
# API Server
SKILLMEAT_API_PORT=8080
SKILLMEAT_API_HOST=0.0.0.0
SKILLMEAT_WORKERS=4
# Web Server
SKILLMEAT_WEB_PORT=3000
NEXT_PUBLIC_API_URL=https://api.example.com # Your production API URL
# Database (PostgreSQL)
DATABASE_URL=postgresql://skillmeat:your-secure-password@postgres:5432/skillmeat
POSTGRES_PASSWORD=your-secure-password
# Authentication (Clerk)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your_publishable_key
CLERK_SECRET_KEY=sk_live_your_secret_key
CLERK_WEBHOOK_SECRET=whsec_your_webhook_secret
# Enterprise features
SKILLMEAT_ENTERPRISE_PAT_SECRET=your-cryptographically-secure-secret
Step 3: Configure PostgreSQL¶
Option A: Use Composed PostgreSQL (Recommended for Single Server)¶
PostgreSQL service is included in docker-compose.yml with profile enterprise. The entrypoint script automatically:
- Creates the database and user
- Runs all migrations
- Seeds initial data if needed
No additional configuration needed—just ensure DATABASE_URL matches:
Option B: External PostgreSQL Instance¶
If using an external PostgreSQL server:
# Create database and user
psql -h postgres.example.com -U postgres -c "CREATE DATABASE skillmeat;"
psql -h postgres.example.com -U postgres -d skillmeat -c "CREATE USER skillmeat WITH PASSWORD 'secure-password';"
psql -h postgres.example.com -U postgres -d skillmeat -c "GRANT ALL PRIVILEGES ON DATABASE skillmeat TO skillmeat;"
# Set DATABASE_URL
DATABASE_URL=postgresql://skillmeat:secure-password@postgres.example.com:5432/skillmeat
Step 4: Configure Clerk Authentication¶
Clerk is the recommended authentication provider for enterprise deployments.
Get Clerk Keys¶
- Create a free account at clerk.com
- Create a new application (choose "Show custom signup/login pages" for self-hosting)
- Copy credentials from API Keys section:
- Publishable Key (
pk_live_...) - Secret Key (
sk_live_...) - Set webhook secret in Clerk dashboard → Webhooks → Endpoints
Add to .env:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your_key
CLERK_SECRET_KEY=sk_live_your_key
CLERK_WEBHOOK_SECRET=whsec_your_secret
Step 5: Start Services¶
With monitoring (recommended for production):
Without monitoring:
Wait ~60 seconds for services to initialize. Check status:
Expected output:
NAME IMAGE STATUS
skillmeat-api-1 skillmeat:latest Up 45 seconds (healthy)
skillmeat-web-1 node:18-alpine Up 40 seconds (healthy)
postgres-1 postgres:15-alpine Up 50 seconds (healthy)
prometheus-1 prom/prometheus Up 10 seconds (healthy)
grafana-1 grafana/grafana Up 15 seconds (healthy)
loki-1 grafana/loki Up 12 seconds (healthy)
Step 6: Access Services¶
| Service | URL | Purpose |
|---|---|---|
| Web UI | http://localhost:3000 | Main application |
| API | http://localhost:8080 | REST API |
| Grafana | http://localhost:3001 | Monitoring dashboards |
| Prometheus | http://localhost:9090 | Metrics exploration |
TLS & Reverse Proxy¶
Using Nginx with Let's Encrypt¶
For production, use a reverse proxy with TLS termination. Example Nginx configuration:
upstream skillmeat_api {
server skillmeat-api:8080;
}
upstream skillmeat_web {
server skillmeat-web:3000;
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
# API routes
location /api/ {
proxy_pass http://skillmeat_api;
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 https;
}
# Web routes
location / {
proxy_pass http://skillmeat_web;
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 https;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Save to nginx.conf, then run:
docker run -d \
-p 80:80 -p 443:443 \
-v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
--network skillmeat_default \
nginx:latest
Using Caddy (Simpler Alternative)¶
Caddy automatically handles TLS with Let's Encrypt:
Create Caddyfile:
example.com {
# API routes
handle /api/* {
reverse_proxy skillmeat-api:8080
}
# Web routes
handle {
reverse_proxy skillmeat-web:3000 {
header_up X-Forwarded-Proto https
}
}
}
Run:
docker run -d \
-p 80:80 -p 443:443 \
-v $(pwd)/Caddyfile:/etc/caddy/Caddyfile \
-v caddy-data:/data \
--network skillmeat_default \
caddy:latest
Database Management¶
Seeding¶
Populate the database with demo artifacts for testing:
# Seed inside the running API container
make db-seed-container
# Or start and seed in one step
make up-enterprise-seed
# Seed from host with DATABASE_URL pointing to the container's PostgreSQL
DATABASE_URL=postgresql://skillmeat:skillmeat@localhost:5432/skillmeat \
make db-seed-enterprise
The seed runner script (scripts/seed-runner.sh) auto-detects enterprise mode from SKILLMEAT_EDITION or DATABASE_URL. Profiles available: full (default), minimal, backstage.
Migrations¶
Migrations run automatically on API startup via the entrypoint script. To manually trigger:
Check migration status:
Accessing PostgreSQL¶
Connect to PostgreSQL directly:
Common queries:
-- List all tables
\dt
-- Check artifact counts
SELECT type, COUNT(*) FROM artifacts GROUP BY type;
-- View database size
SELECT pg_size_pretty(pg_database_size('skillmeat'));
-- Check active connections
SELECT datname, count(*) FROM pg_stat_activity GROUP BY datname;
-- Exit
\q
Backup¶
Create automated PostgreSQL backups:
# Manual backup
./compose.sh exec postgres pg_dump -U skillmeat skillmeat > backup-$(date +%Y%m%d-%H%M%S).sql
# Store in backups directory
mkdir -p backups
./compose.sh exec postgres pg_dump -U skillmeat skillmeat | gzip > backups/skillmeat-$(date +%Y%m%d-%H%M%S).sql.gz
# Verify backup
ls -lh backups/
Restore¶
Restore from backup:
# Stop API (prevents concurrent writes)
./compose.sh stop api
# Restore database
zcat backups/skillmeat-YYYYMMDD-HHMMSS.sql.gz | ./compose.sh exec -T postgres psql -U skillmeat skillmeat
# Restart API
./compose.sh start api
Monitoring & Observability¶
Prometheus Metrics¶
Prometheus collects metrics from all services. Access at http://localhost:9090.
Common queries:
# API request rate (requests per second)
rate(http_requests_total[1m])
# API response time (95th percentile)
histogram_quantile(0.95, http_request_duration_seconds_bucket)
# API errors
rate(http_requests_total{status=~"5.."}[1m])
# Database connection pool
skillmeat_db_pool_size
Grafana Dashboards¶
Grafana provides visual dashboards. Access at http://localhost:3001.
Default credentials:
- Username: admin
- Password: admin (change immediately in production)
Pre-loaded dashboards: - SkillMeat API: Request rate, errors, latency - PostgreSQL: Connection pool, query performance - System: CPU, memory, disk usage
Change Admin Password¶
Loki Logs¶
Loki aggregates logs from all services. Query in Grafana → Explore → Loki.
Example queries:
# All API logs
{job="skillmeat-api"}
# Errors only
{job="skillmeat-api"} | json | level="ERROR"
# Web app logs
{job="skillmeat-web"}
Security Hardening¶
Network Security¶
Restrict API to internal network only:
Edit .env:
Then access only through the reverse proxy.
Database Security¶
Use strong passwords and limit connections:
# Change default password
POSTGRES_PASSWORD=your-very-secure-password-here-64-chars-min
# Limit concurrent connections
./compose.sh exec postgres psql -U skillmeat -d skillmeat -c \
"ALTER ROLE skillmeat WITH CONNECTION LIMIT 20;"
Rate Limiting¶
Configure in reverse proxy (Nginx/Caddy) for API routes:
Nginx example:
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
location /api/ {
limit_req zone=api_limit burst=10;
proxy_pass http://skillmeat_api;
}
Secrets Management¶
Never commit .env to version control. Use environment variable secrets:
Store in: - Docker Swarm: Use secrets - Kubernetes: Use secrets or sealed-secrets - CI/CD: Use provider's secrets manager
Audit Logging¶
Enable detailed API logging:
All API requests are logged. View logs:
Performance Tuning¶
Database Connection Pool¶
Tune for your workload:
API Workers¶
Scale horizontally by increasing workers:
# 4 workers (good for 4+ CPU cores)
SKILLMEAT_WORKERS=4
# 8 workers (for high-traffic deployments)
SKILLMEAT_WORKERS=8
Monitor actual usage:
PostgreSQL Tuning¶
For high-traffic deployments, tune PostgreSQL:
./compose.sh exec postgres psql -U skillmeat -d skillmeat << EOF
ALTER SYSTEM SET shared_buffers = '2GB';
ALTER SYSTEM SET effective_cache_size = '6GB';
ALTER SYSTEM SET work_mem = '50MB';
EOF
./compose.sh restart postgres
Scaling¶
Horizontal Scaling (Multiple API Instances)¶
To scale API horizontally, add replicas in docker-compose.prod.yml or a custom override:
Then restart with compose:
Update reverse proxy to load-balance across replicas.
Load Balancing¶
Configure round-robin in Nginx:
upstream skillmeat_api {
server skillmeat-api-1:8080;
server skillmeat-api-2:8080;
server skillmeat-api-3:8080;
}
Troubleshooting¶
Database Connection Fails¶
Check PostgreSQL is running:
Verify connection string in .env:
# Format: postgresql://user:password@host:port/database
DATABASE_URL=postgresql://skillmeat:password@postgres:5432/skillmeat
Migrations Timeout¶
Increase startup timeout:
# In docker-compose.prod.yml or custom override
services:
api:
healthcheck:
timeout: 30
start_period: 120
Clerk Authentication Fails¶
Verify keys are correct:
# Check keys are set
env | grep CLERK
# Restart web service to pick up changes
./compose.sh restart web
View Clerk logs:
Out of Memory¶
Monitor memory usage:
If PostgreSQL is using too much memory:
./compose.sh exec postgres psql -U skillmeat -d skillmeat -c \
"SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC LIMIT 10;"
Maintenance¶
Update Services¶
Pull latest images and restart:
Health Checks¶
All services include health checks. View status:
Log Rotation¶
Docker automatically rotates container logs. Configure in docker-compose.prod.yml or custom override:
High Availability (Advanced)¶
For production high-availability setups:
- Database: Use PostgreSQL streaming replication or managed PostgreSQL service
- API: Run multiple replicas behind load balancer
- Web: Run multiple replicas (stateless)
- Monitoring: Use external monitoring service (DataDog, New Relic, etc.)
Contact support for HA reference architectures.
Next Steps¶
- Configuration Reference — All environment variables
- Local Deployment — For testing before production
- Development Guide — Development setup
- Main Deployment Guide — Choose deployment pattern
Support¶
For enterprise deployment support:
- Check this guide's Troubleshooting section
- Review Configuration Reference
- View logs:
./compose.sh logs -f - Check PostgreSQL health:
./compose.sh exec postgres pg_isready