Files
StarPunk/docs/deployment/container-deployment.md
Phil Skentelbery 8d593ca1b9 docs: add container deployment guide and implementation report
Complete Phase 5 containerization documentation:
- Add comprehensive container deployment guide (500+ lines)
- Document Podman and Docker deployment workflows
- Include reverse proxy setup for Caddy and Nginx
- Add troubleshooting, monitoring, and maintenance sections
- Document --userns=keep-id requirement for Podman
- Add backup/restore procedures
- Include performance tuning guidelines
- Add security best practices

Implementation report includes:
- Technical implementation details
- Testing results and metrics
- Challenge resolution (Podman permissions)
- Security and compliance verification
- Integration with RSS feed
- Lessons learned and recommendations

Updated CHANGELOG.md:
- Document container features in v0.6.0
- Add configuration variables
- List deployment capabilities
- Note Podman and Docker compatibility

Phase 5 containerization: 100% complete
2025-11-19 10:14:35 -07:00

12 KiB
Raw Blame History

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
  • 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

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

mkdir -p container-data/notes

3. Configure Environment

cp .env.example .env
# Edit .env with your values:
nano .env

Required settings:

SITE_URL=https://your-domain.com
SITE_NAME=Your Site Name
ADMIN_ME=https://your-identity.com
SESSION_SECRET=<generate-random-secret>

Generate session secret:

python3 -c "import secrets; print(secrets.token_hex(32))"

4. Run the Container

Using Podman

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

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

# Check health endpoint
curl http://localhost:8000/health

# Expected output:
# {"status": "healthy", "version": "0.6.0", "environment": "production"}

Container Orchestration

The included compose.yaml provides a complete orchestration configuration.

Podman Compose

Install podman-compose (if not installed):

pip install podman-compose

Run:

podman-compose up -d

View logs:

podman-compose logs -f

Stop:

podman-compose down

Docker Compose

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

Advantages:

  • Automatic HTTPS with Let's Encrypt
  • Minimal configuration
  • Built-in security headers

Installation:

# 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:

# 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):

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:

sudo apt install nginx certbot python3-certbot-nginx

Configuration:

# 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:

# 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=<your-random-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:

# 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):

# 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

# 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:

curl http://localhost:8000/health

Response:

{
  "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:

# View health status
podman inspect starpunk | grep -A 5 Health

# Docker
docker inspect starpunk | grep -A 5 Health

Log Monitoring

View logs:

# 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:

podman logs starpunk

Common issues:

  1. Port already in use:

    # 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:

    # 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:

    # 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:

# 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:

# 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:

# Check bind mount
podman inspect starpunk | grep -A 5 Mounts

# Should show:
# "Source": "/path/to/container-data"
# "Destination": "/data"

Test persistence:

# 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:

environment:
  - WORKERS=8  # For 4 CPU cores

Memory Limits

Default limits in compose.yaml:

deploy:
  resources:
    limits:
      cpus: '1.0'
      memory: 512M

Increase for high-traffic sites:

deploy:
  resources:
    limits:
      cpus: '2.0'
      memory: 1G

Database Optimization

For sites with many notes (>1000):

# 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:

podman exec starpunk whoami
# Output: starpunk

2. Network Isolation

Bind to localhost only:

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:

python3 -c "import secrets; print(secrets.token_hex(32))"

4. Regular Updates

Update base image:

# Rebuild with latest Python 3.11
podman build --no-cache -t starpunk:0.6.0 -f Containerfile .

Update dependencies:

# 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

# 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

External Resources

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