Single VM Deployment
This guide covers deploying UAPK Gateway on a single virtual machine, suitable for small to medium deployments.
Prerequisites
- Ubuntu 22.04 LTS or Debian 12
- 4+ GB RAM, 2+ CPU cores
- 50+ GB SSD storage
- Domain name pointing to server IP
- SSH access
Step 1: Initial Setup
# Update system
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y curl git ufw fail2ban
# Configure firewall
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
# Create service user
sudo useradd -r -s /bin/false uapk
Step 2: Install Docker
# Install Docker
curl -fsSL https://get.docker.com | sudo sh
# Add current user to docker group
sudo usermod -aG docker $USER
# Enable Docker
sudo systemctl enable docker
sudo systemctl start docker
# Install Docker Compose
sudo apt install -y docker-compose-plugin
Step 3: Configure PostgreSQL
# Create data directory
sudo mkdir -p /var/lib/uapk-gateway/postgres
sudo chown 999:999 /var/lib/uapk-gateway/postgres
# Generate database password
DB_PASSWORD=$(openssl rand -base64 32)
echo "Database password: $DB_PASSWORD"
Step 4: Generate Signing Key
# Create keys directory
sudo mkdir -p /etc/uapk-gateway/keys
sudo chown uapk:uapk /etc/uapk-gateway/keys
# Generate Ed25519 key pair
python3 -c "
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
private_key = Ed25519PrivateKey.generate()
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
print(private_pem.decode())
" | sudo tee /etc/uapk-gateway/keys/signing.pem
sudo chmod 600 /etc/uapk-gateway/keys/signing.pem
sudo chown uapk:uapk /etc/uapk-gateway/keys/signing.pem
Step 5: Create Docker Compose File
sudo mkdir -p /opt/uapk-gateway
cd /opt/uapk-gateway
# Create docker-compose.yml
cat <<EOF | sudo tee docker-compose.yml
version: '3.8'
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: uapk_gateway
POSTGRES_USER: uapk
POSTGRES_PASSWORD: \${DB_PASSWORD}
volumes:
- /var/lib/uapk-gateway/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U uapk -d uapk_gateway"]
interval: 10s
timeout: 5s
retries: 5
gateway:
image: ghcr.io/uapk/gateway:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://uapk:\${DB_PASSWORD}@db:5432/uapk_gateway
SECRET_KEY: \${SECRET_KEY}
GATEWAY_SIGNING_KEY_FILE: /keys/signing.pem
GATEWAY_ENV: production
LOG_LEVEL: INFO
volumes:
- /etc/uapk-gateway/keys:/keys:ro
ports:
- "127.0.0.1:8000:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/gateway/health"]
interval: 30s
timeout: 10s
retries: 3
caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
- gateway
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
EOF
Step 6: Create Caddyfile
# Replace gateway.yourdomain.com with your domain
cat <<EOF | sudo tee /opt/uapk-gateway/Caddyfile
gateway.yourdomain.com {
reverse_proxy gateway:8000
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
log {
output file /var/log/caddy/access.log
}
}
EOF
Step 7: Create Environment File
# Generate secret key
SECRET_KEY=$(openssl rand -base64 32)
# Create .env file
cat <<EOF | sudo tee /opt/uapk-gateway/.env
DB_PASSWORD=${DB_PASSWORD}
SECRET_KEY=${SECRET_KEY}
EOF
sudo chmod 600 /opt/uapk-gateway/.env
Step 8: Start Services
cd /opt/uapk-gateway
# Start services
sudo docker compose up -d
# Check status
sudo docker compose ps
# View logs
sudo docker compose logs -f gateway
Step 9: Initialize Gateway
# Wait for services to be healthy
sleep 10
# Create initial organization
curl -X POST http://localhost:8000/api/v1/orgs \
-H "Content-Type: application/json" \
-d '{
"name": "My Organization",
"slug": "my-org",
"admin_email": "admin@example.com",
"admin_password": "secure-password-here"
}'
# Save the org_id from response
Step 10: Set Up Systemd Service
# Create systemd service
cat <<EOF | sudo tee /etc/systemd/system/uapk-gateway.service
[Unit]
Description=UAPK Gateway
Requires=docker.service
After=docker.service
[Service]
Type=simple
WorkingDirectory=/opt/uapk-gateway
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Enable and start
sudo systemctl enable uapk-gateway
sudo systemctl start uapk-gateway
Step 11: Set Up Log Rotation
cat <<EOF | sudo tee /etc/logrotate.d/uapk-gateway
/var/log/caddy/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
}
EOF
Step 12: Configure Backups
# Create backup script
cat <<'EOF' | sudo tee /opt/uapk-gateway/backup.sh
#!/bin/bash
set -e
BACKUP_DIR=/var/backups/uapk-gateway
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p $BACKUP_DIR
# Backup database
docker compose exec -T db pg_dump -U uapk uapk_gateway | gzip > $BACKUP_DIR/db-$DATE.sql.gz
# Backup keys
cp -r /etc/uapk-gateway/keys $BACKUP_DIR/keys-$DATE
# Keep last 7 days
find $BACKUP_DIR -type f -mtime +7 -delete
echo "Backup completed: $DATE"
EOF
sudo chmod +x /opt/uapk-gateway/backup.sh
# Add to crontab
echo "0 2 * * * /opt/uapk-gateway/backup.sh >> /var/log/uapk-backup.log 2>&1" | sudo crontab -
Verification
# Check services
sudo docker compose ps
# Check gateway health
curl https://gateway.yourdomain.com/api/v1/gateway/health
# Check TLS certificate
curl -vI https://gateway.yourdomain.com 2>&1 | grep "SSL certificate"
Updating
cd /opt/uapk-gateway
# Pull latest image
sudo docker compose pull
# Restart with new image
sudo docker compose up -d
# Verify health
sudo docker compose ps
Troubleshooting
Service Won't Start
# Check logs
sudo docker compose logs gateway
# Check database
sudo docker compose exec db psql -U uapk -d uapk_gateway -c "SELECT 1"
TLS Certificate Issues
# Check Caddy logs
sudo docker compose logs caddy
# Force certificate renewal
sudo docker compose exec caddy caddy reload
Performance Issues
# Check resource usage
docker stats
# Check database connections
sudo docker compose exec db psql -U uapk -d uapk_gateway \
-c "SELECT count(*) FROM pg_stat_activity"
Related
- Caddy Setup - Detailed Caddy configuration
- Backups - Backup strategies
- Monitoring - Setting up monitoring