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
660 lines
12 KiB
Markdown
660 lines
12 KiB
Markdown
# 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-random-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=<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**:
|
||
```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
|