Files
punimtag/docs/DEPLOYMENT.md
T
ilia 31e44cc737
CI / skip-ci-check (pull_request) Successful in 31s
CI / docker-ci (pull_request) Successful in 33s
CI / python-lint (pull_request) Successful in 34s
CI / secret-scan (pull_request) Successful in 38s
CI / admin-unit (pull_request) Successful in 57s
CI / viewer-unit (pull_request) Successful in 1m37s
CI / e2e (pull_request) Successful in 1m56s
Scrub active admin guides for plainer tone
Rewrite docs/USER_GUIDE.md, docs/DEPLOYMENT.md, docs/DEPLOY_FROM_SCRATCH.md,
and DEPLOYMENT_CHECKLIST.md to drop AI-generated tells: bold-colon feature
walls, Title Case headings, em dashes, and rule-of-three lists. Facts
(commands, paths, ports, URLs) are unchanged. Historical scratch docs
(PHOTO_VIEWER_*, FIX_*, *_REVIEW, *_PLAN, etc.) and the cPanel-specific
runbooks are untouched.
2026-08-05 17:05:21 -04:00

11 KiB

Deployment

Deploy PunimTag to development or production. Homelab host and Caddy details live in the private ansible repo.

Last reviewed: January 6, 2026.

Table of contents

  1. Prerequisites
  2. Development server deployment
  3. Production deployment
  4. Environment configuration
  5. Service management
  6. Troubleshooting

Prerequisites

Server requirements

  • Linux (Ubuntu 20.04+ recommended)
  • Python 3.12 or higher
  • Node.js 18+ and npm 9+
  • PostgreSQL 12+ (required)
  • Redis 6+ (required for background jobs)
  • At least 4GB RAM (8GB+ recommended for large photo collections)
  • Enough disk space for photos and the database

Development server information

Development server: host <backend-host>, user appuser. Contact the administrator for the password.

Development database: host <db-host>, port 5432, user <db-user>. Contact the administrator for the password.


Development server deployment

Step 1: build applications

On your local machine or CI/CD pipeline:

# Install dependencies (if not already done)
npm run install:all

# Build all frontends
npm run build:all

# Or use the deployment script
npm run deploy:dev

This builds admin-frontend and viewer-frontend for production and prepares the deployment package.

Step 2: transfer files to server

# Create deployment directory structure
ssh appuser@<backend-host> "mkdir -p /opt/punimtag/{backend,admin-frontend,viewer-frontend,data/uploads,logs}"

# Transfer backend
scp -r backend/* appuser@<backend-host>:/opt/punimtag/backend/
scp requirements.txt appuser@<backend-host>:/opt/punimtag/

# Transfer built frontends
scp -r admin-frontend/dist/* appuser@<backend-host>:/opt/punimtag/admin-frontend/
scp -r viewer-frontend/.next/* appuser@<backend-host>:/opt/punimtag/viewer-frontend/

# Transfer configuration files
scp .env.example appuser@<backend-host>:/opt/punimtag/.env

Step 3: server setup

SSH into the development server:

ssh appuser@<backend-host>
cd /opt/punimtag

Install system dependencies

# Update package list
sudo apt update

# Install PostgreSQL client (if not already installed)
sudo apt install -y postgresql-client

# Install Redis (if not already installed)
sudo apt install -y redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server

# Install Python dependencies
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Configure environment

Edit the .env file:

nano .env

Set the following variables:

# Development Database
DATABASE_URL=postgresql+psycopg2://<db-user>:[PASSWORD]@<db-host>:5432/punimtag
DATABASE_URL_AUTH=postgresql+psycopg2://<db-user>:[PASSWORD]@<db-host>:5432/punimtag_auth

# JWT Secrets (change in production!)
SECRET_KEY=dev-secret-key-change-in-production
ADMIN_USERNAME=admin
ADMIN_PASSWORD=<choose-a-strong-password>

# Photo storage
PHOTO_STORAGE_DIR=/opt/punimtag/data/uploads

# Redis
REDIS_URL=redis://localhost:6379/0

# API Configuration
API_HOST=0.0.0.0
API_PORT=8000

Configure frontend environment

Admin frontend: create admin-frontend/.env:

VITE_API_URL=http://<backend-host>:8000

Viewer frontend: create viewer-frontend/.env:

DATABASE_URL=postgresql://<db-user>:[PASSWORD]@<db-host>:5432/punimtag
DATABASE_URL_AUTH=postgresql://<db-user>:[PASSWORD]@<db-host>:5432/punimtag_auth
NEXTAUTH_URL=http://<backend-host>:3001
NEXTAUTH_SECRET=dev-secret-key-change-in-production

Generate Prisma client:

cd viewer-frontend
npx prisma generate
cd ..

Step 4: set up systemd services

Create systemd service files for automatic startup:

Backend API service

sudo nano /etc/systemd/system/punimtag-api.service
[Unit]
Description=PunimTag API Server
After=network.target postgresql.service redis.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/punimtag
Environment="PATH=/opt/punimtag/venv/bin"
Environment="PYTHONPATH=/opt/punimtag"
ExecStart=/opt/punimtag/venv/bin/uvicorn backend.app:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

RQ worker service

sudo nano /etc/systemd/system/punimtag-worker.service
[Unit]
Description=PunimTag RQ Worker
After=network.target postgresql.service redis.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/punimtag
Environment="PATH=/opt/punimtag/venv/bin"
Environment="PYTHONPATH=/opt/punimtag"
ExecStart=/opt/punimtag/venv/bin/python -m rq worker --url redis://localhost:6379/0
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Admin frontend service (using PM2 or nginx)

For production, use nginx to serve the built frontend. Alternatively, use PM2:

npm install -g pm2
cd /opt/punimtag/admin-frontend
pm2 serve dist 3000 --name punimtag-admin
pm2 save
pm2 startup

Viewer frontend service

cd /opt/punimtag/viewer-frontend
pm2 start npm --name punimtag-viewer -- start
pm2 save

Step 5: start services

# Enable and start services
sudo systemctl enable punimtag-api
sudo systemctl enable punimtag-worker
sudo systemctl start punimtag-api
sudo systemctl start punimtag-worker

# Check status
sudo systemctl status punimtag-api
sudo systemctl status punimtag-worker

Step 6: verify deployment

  1. Check API health: curl http://<backend-host>:8000/api/v1/health
  2. Check API documentation: open http://<backend-host>:8000/docs in a browser.
  3. Access the admin frontend: open http://<backend-host>:3000.
  4. Access the viewer frontend: open http://<backend-host>:3001.

Production deployment

Additional production considerations

Security: change all default passwords, use strong JWT secrets, enable HTTPS with SSL certificates, configure firewall rules, restrict database access, and use read-only database users where possible.

Performance: use nginx as a reverse proxy, enable gzip compression, configure caching headers, use a CDN for static assets if you want one, set up database connection pooling, and configure Redis persistence.

Monitoring: set up log aggregation, configure health check endpoints, set up alerting for service failures, and monitor database performance and API response times.

Backup: automate database backups, photo storage backups, and configuration file backups, and have a disaster recovery plan.

Nginx configuration example

# /etc/nginx/sites-available/punimtag

# Admin Frontend
server {
    listen 80;
    server_name admin.punimtag.example.com;

    root /opt/punimtag/admin-frontend/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Viewer Frontend
server {
    listen 80;
    server_name viewer.punimtag.example.com;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# API (optional, if exposing directly)
server {
    listen 80;
    server_name api.punimtag.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Environment configuration

Required environment variables

Backend (.env):

# Database (PostgreSQL - required)
DATABASE_URL=postgresql+psycopg2://user:password@host:5432/punimtag
DATABASE_URL_AUTH=postgresql+psycopg2://user:password@host:5432/punimtag_auth

# JWT Configuration
SECRET_KEY=your-secret-key-here
ADMIN_USERNAME=admin
ADMIN_PASSWORD=secure-password

# Photo Storage
PHOTO_STORAGE_DIR=/path/to/photos

# Redis
REDIS_URL=redis://localhost:6379/0

# API Configuration
API_HOST=0.0.0.0
API_PORT=8000

Admin frontend (admin-frontend/.env):

VITE_API_URL=http://your-api-url:8000

Viewer frontend (viewer-frontend/.env):

DATABASE_URL=postgresql://user:password@host:5432/punimtag
DATABASE_URL_AUTH=postgresql://user:password@host:5432/punimtag_auth
NEXTAUTH_URL=http://your-viewer-url:3001
NEXTAUTH_SECRET=your-nextauth-secret

Service management

Systemd commands

# Start services
sudo systemctl start punimtag-api
sudo systemctl start punimtag-worker

# Stop services
sudo systemctl stop punimtag-api
sudo systemctl stop punimtag-worker

# Restart services
sudo systemctl restart punimtag-api
sudo systemctl restart punimtag-worker

# Check status
sudo systemctl status punimtag-api
sudo systemctl status punimtag-worker

# View logs
sudo journalctl -u punimtag-api -f
sudo journalctl -u punimtag-worker -f

PM2 commands

# Start services
pm2 start punimtag-admin
pm2 start punimtag-viewer

# Stop services
pm2 stop punimtag-admin
pm2 stop punimtag-viewer

# Restart services
pm2 restart punimtag-admin
pm2 restart punimtag-viewer

# View logs
pm2 logs punimtag-admin
pm2 logs punimtag-viewer

# Monitor
pm2 monit

Troubleshooting

Common issues

Database connection errors: verify PostgreSQL is running, check the connection string in .env, verify the database exists and the user has permissions, and check firewall rules.

Redis connection errors: verify Redis is running (redis-cli ping), check the Redis URL in .env, and verify Redis is reachable.

API not starting: check logs (sudo journalctl -u punimtag-api -n 50), verify the Python virtual environment is activated, check that PYTHONPATH is set correctly, and verify all dependencies are installed.

Frontend build errors: clear node_modules and reinstall, check the Node.js version (18+), and verify all environment variables are set.

Worker not processing jobs: check worker logs (sudo journalctl -u punimtag-worker -f), verify the Redis connection, check the job queue (redis-cli), and restart the worker service.

Log locations

  • Systemd logs: sudo journalctl -u <service-name>
  • PM2 logs: pm2 logs
  • Application logs: /opt/punimtag/logs/ (if configured)

Health checks

# API health
curl http://localhost:8000/api/v1/health

# Redis
redis-cli ping

# PostgreSQL
psql -h <db-host> -U <db-user> -d punimtag -c "SELECT 1;"

Deployment checklist

  • Build all frontends
  • Transfer files to server
  • Install system dependencies
  • Set up Python virtual environment
  • Configure environment variables
  • Set up systemd services
  • Configure nginx (production)
  • Set up SSL certificates (production)
  • Configure firewall rules
  • Test all services
  • Set up monitoring
  • Configure backups
  • Document deployment process

For additional help, see README.md (main documentation), docs/ARCHITECTURE.md (system architecture), and docs/USER_GUIDE.md (user guide).