# StarPunk Container Deployment Guide **Version**: 0.6.0 **Last Updated**: 2025-11-19 ## Overview This guide covers deploying StarPunk in a production environment using containers (Podman or Docker). StarPunk is packaged as a lightweight, production-ready container image that includes: - Python 3.11 runtime - Gunicorn WSGI server (4 workers) - Multi-stage build for optimized size (174MB) - Non-root user security - Health check endpoint - Volume mounts for data persistence ## Prerequisites ### Required - **Container Runtime**: Podman 3.0+ or Docker 20.10+ - **Storage**: Minimum 500MB for image + data - **Memory**: Minimum 512MB RAM (recommended 1GB) - **Network**: Port 8000 available for container ### Recommended - **Reverse Proxy**: Caddy 2.0+ or Nginx 1.18+ - **TLS Certificate**: Let's Encrypt via certbot or Caddy auto-HTTPS - **Domain**: Public domain name for HTTPS and IndieAuth ## Quick Start ### 1. Build the Container ```bash cd /path/to/starpunk podman build -t starpunk:0.6.0 -f Containerfile . ``` **Expected output**: - Build completes in 2-3 minutes - Final image size: ~174MB - Multi-stage build optimizes dependencies ### 2. Prepare Data Directory ```bash mkdir -p container-data/notes ``` ### 3. Configure Environment ```bash cp .env.example .env # Edit .env with your values: nano .env ``` **Required settings**: ```bash SITE_URL=https://your-domain.com SITE_NAME=Your Site Name ADMIN_ME=https://your-identity.com SESSION_SECRET= ``` **Generate session secret**: ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` ### 4. Run the Container #### Using Podman ```bash podman run -d \ --name starpunk \ --userns=keep-id \ -p 127.0.0.1:8000:8000 \ -v $(pwd)/container-data:/data:rw \ --env-file .env \ starpunk:0.6.0 ``` **Note**: The `--userns=keep-id` flag is **required** for Podman to properly handle file permissions with volume mounts. #### Using Docker ```bash docker run -d \ --name starpunk \ -p 127.0.0.1:8000:8000 \ -v $(pwd)/container-data:/data:rw \ --env-file .env \ starpunk:0.6.0 ``` ### 5. Verify Container is Running ```bash # Check health endpoint curl http://localhost:8000/health # Expected output: # {"status": "healthy", "version": "0.6.0", "environment": "production"} ``` ## Container Orchestration ### Using Compose (Recommended) The included `compose.yaml` provides a complete orchestration configuration. #### Podman Compose **Install podman-compose** (if not installed): ```bash pip install podman-compose ``` **Run**: ```bash podman-compose up -d ``` **View logs**: ```bash podman-compose logs -f ``` **Stop**: ```bash podman-compose down ``` #### Docker Compose ```bash docker compose up -d docker compose logs -f docker compose down ``` ### Compose Configuration The `compose.yaml` includes: - Automatic restart policy - Health checks - Resource limits (1 CPU, 512MB RAM) - Log rotation (10MB max, 3 files) - Network isolation - Volume persistence ## Production Deployment ### Architecture ``` Internet → HTTPS (443) ↓ Reverse Proxy (Caddy/Nginx) ↓ HTTP (8000) → Container ↓ Volume Mount → /data (persistent storage) ``` ### Reverse Proxy Setup #### Option 1: Caddy (Recommended) **Advantages**: - Automatic HTTPS with Let's Encrypt - Minimal configuration - Built-in security headers **Installation**: ```bash # Install Caddy sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy ``` **Configuration**: ```bash # Copy example config cp Caddyfile.example Caddyfile # Edit domain nano Caddyfile # Replace "your-domain.com" with your actual domain # Run Caddy sudo systemctl enable --now caddy ``` **Caddyfile** (minimal): ```caddy your-domain.com { reverse_proxy localhost:8000 } ``` Caddy will automatically: - Obtain SSL certificate from Let's Encrypt - Redirect HTTP to HTTPS - Renew certificates before expiry #### Option 2: Nginx **Installation**: ```bash sudo apt install nginx certbot python3-certbot-nginx ``` **Configuration**: ```bash # Copy example config sudo cp nginx.conf.example /etc/nginx/sites-available/starpunk # Edit domain sudo nano /etc/nginx/sites-available/starpunk # Replace "your-domain.com" with your actual domain # Enable site sudo ln -s /etc/nginx/sites-available/starpunk /etc/nginx/sites-enabled/ # Test configuration sudo nginx -t # Obtain SSL certificate sudo certbot --nginx -d your-domain.com # Reload Nginx sudo systemctl reload nginx ``` ### Environment Configuration for Production Update `.env` for production: ```bash # Site Configuration SITE_URL=https://your-domain.com SITE_NAME=Your Site Name SITE_AUTHOR=Your Name SITE_DESCRIPTION=Your site description # Authentication ADMIN_ME=https://your-identity.com SESSION_SECRET= # Flask Configuration FLASK_ENV=production FLASK_DEBUG=0 # Container paths (these are set by compose.yaml) DATA_PATH=/data NOTES_PATH=/data/notes DATABASE_PATH=/data/starpunk.db # RSS Feed FEED_MAX_ITEMS=50 FEED_CACHE_SECONDS=300 # Application VERSION=0.6.0 ENVIRONMENT=production ``` **Important**: Never set `DEV_MODE=true` in production! ## Data Persistence ### Volume Mounts All application data is stored in the mounted volume: ``` container-data/ ├── notes/ # Markdown note files └── starpunk.db # SQLite database ``` ### Backup Strategy **Manual Backup**: ```bash # Create timestamped backup tar -czf starpunk-backup-$(date +%Y%m%d).tar.gz container-data/ # Copy to safe location cp starpunk-backup-*.tar.gz /backup/location/ ``` **Automated Backup** (cron): ```bash # Add to crontab crontab -e # Daily backup at 2 AM 0 2 * * * cd /path/to/starpunk && tar -czf /backup/starpunk-$(date +\%Y\%m\%d).tar.gz container-data/ ``` ### Restore from Backup ```bash # Stop container podman stop starpunk podman rm starpunk # Restore data rm -rf container-data tar -xzf starpunk-backup-20251119.tar.gz # Restart container podman-compose up -d ``` ## Health Checks and Monitoring ### Health Check Endpoint The container includes a `/health` endpoint that checks: - Database connectivity - Filesystem access - Application state **Usage**: ```bash curl http://localhost:8000/health ``` **Response**: ```json { "status": "healthy", "version": "0.6.0", "environment": "production" } ``` **Status Codes**: - `200`: Application healthy - `500`: Application unhealthy (check logs) ### Container Health Check The Containerfile includes an automatic health check that runs every 30 seconds: ```bash # View health status podman inspect starpunk | grep -A 5 Health # Docker docker inspect starpunk | grep -A 5 Health ``` ### Log Monitoring **View logs**: ```bash # Real-time logs podman logs -f starpunk # Last 100 lines podman logs --tail 100 starpunk # Docker docker logs -f starpunk ``` **Log rotation** is configured in `compose.yaml`: - Max size: 10MB per file - Max files: 3 - Total max: 30MB ## Troubleshooting ### Container Won't Start **Check logs**: ```bash podman logs starpunk ``` **Common issues**: 1. **Port already in use**: ```bash # Find process using port 8000 lsof -i :8000 # Change port in compose.yaml or run command -p 127.0.0.1:8080:8000 ``` 2. **Permission denied on volume**: ```bash # Podman: Use --userns=keep-id podman run --userns=keep-id ... # Or fix ownership chown -R $(id -u):$(id -g) container-data ``` 3. **Database initialization fails**: ```bash # Check volume mount podman inspect starpunk | grep Mounts -A 10 # Verify directory exists ls -la container-data/ ``` ### Health Check Fails **Symptoms**: `curl http://localhost:8000/health` returns error or no response **Checks**: ```bash # 1. Is container running? podman ps | grep starpunk # 2. Check container logs podman logs starpunk | tail -20 # 3. Verify port binding podman port starpunk # 4. Test from inside container podman exec starpunk curl localhost:8000/health ``` ### IndieAuth Not Working **Requirements**: - SITE_URL must be HTTPS (not HTTP) - SITE_URL must match your public domain exactly - ADMIN_ME must be a valid IndieAuth identity **Test**: ```bash # Verify SITE_URL in container podman exec starpunk env | grep SITE_URL # Should output: SITE_URL=https://your-domain.com ``` ### Data Not Persisting **Verify volume mount**: ```bash # Check bind mount podman inspect starpunk | grep -A 5 Mounts # Should show: # "Source": "/path/to/container-data" # "Destination": "/data" ``` **Test persistence**: ```bash # Create test file podman exec starpunk touch /data/test.txt # Stop and remove container podman stop starpunk && podman rm starpunk # Check if file exists on host ls -la container-data/test.txt # Restart container podman-compose up -d # Verify file still exists podman exec starpunk ls /data/test.txt ``` ## Performance Tuning ### Worker Configuration The default configuration uses 4 Gunicorn workers. Adjust based on CPU cores: **Formula**: `workers = (2 × CPU_cores) + 1` **Update in compose.yaml**: ```yaml environment: - WORKERS=8 # For 4 CPU cores ``` ### Memory Limits Default limits in `compose.yaml`: ```yaml deploy: resources: limits: cpus: '1.0' memory: 512M ``` **Increase for high-traffic sites**: ```yaml deploy: resources: limits: cpus: '2.0' memory: 1G ``` ### Database Optimization For sites with many notes (>1000): ```bash # Run SQLite VACUUM periodically podman exec starpunk sqlite3 /data/starpunk.db "VACUUM;" # Add to cron (monthly) 0 3 1 * * podman exec starpunk sqlite3 /data/starpunk.db "VACUUM;" ``` ## Security Best Practices ### 1. Non-Root User The container runs as user `starpunk` (UID 1000), not root. **Verify**: ```bash podman exec starpunk whoami # Output: starpunk ``` ### 2. Network Isolation Bind to localhost only: ```yaml ports: - "127.0.0.1:8000:8000" # ✓ Secure # Not: "8000:8000" # ✗ Exposes to internet ``` ### 3. Secrets Management **Never commit `.env` to version control!** **Generate strong secrets**: ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` ### 4. Regular Updates **Update base image**: ```bash # Rebuild with latest Python 3.11 podman build --no-cache -t starpunk:0.6.0 -f Containerfile . ``` **Update dependencies**: ```bash # Update requirements.txt uv pip compile requirements.txt --upgrade # Rebuild container podman build -t starpunk:0.6.0 -f Containerfile . ``` ### 5. TLS/HTTPS Only **Required for IndieAuth!** - Use reverse proxy with HTTPS - Set `SITE_URL=https://...` (not http://) - Enforce HTTPS redirects ## Maintenance ### Regular Tasks **Weekly**: - Check logs for errors - Verify backups are running - Monitor disk space **Monthly**: - Update dependencies and rebuild - Vacuum SQLite database - Review resource usage **Quarterly**: - Security audit - Review and rotate secrets - Test backup restore procedure ### Updating StarPunk ```bash # 1. Backup data tar -czf backup-pre-update.tar.gz container-data/ # 2. Stop container podman stop starpunk podman rm starpunk # 3. Pull/build new version git pull podman build -t starpunk:0.7.0 -f Containerfile . # 4. Update compose.yaml version sed -i 's/starpunk:0.6.0/starpunk:0.7.0/' compose.yaml # 5. Restart podman-compose up -d # 6. Verify curl http://localhost:8000/health ``` ## Resources ### Documentation - [Phase 5 Design](../designs/phase-5-rss-and-container.md) - [Containerfile](../../Containerfile) - [Compose Configuration](../../compose.yaml) - [Caddy Example](../../Caddyfile.example) - [Nginx Example](../../nginx.conf.example) ### External Resources - [Podman Documentation](https://docs.podman.io/) - [Docker Documentation](https://docs.docker.com/) - [Gunicorn Configuration](https://docs.gunicorn.org/) - [Caddy Documentation](https://caddyserver.com/docs/) - [Nginx Documentation](https://nginx.org/en/docs/) ## Support For issues or questions: - Check this documentation first - Review container logs: `podman logs starpunk` - Verify health endpoint: `curl http://localhost:8000/health` - Check GitHub issues (if project is on GitHub) --- **Document Version**: 1.0 **StarPunk Version**: 0.6.0 **Last Updated**: 2025-11-19