# Deployment Guide — Shumiland VPS ## Prerequisites - VPS with Ubuntu 22.04+, Docker + Docker Compose v2 installed - Domain `shumiland.com.ua` DNS A-record pointing to VPS IP - SSH access as non-root user with sudo - GitHub Actions secrets configured (see CI/CD section) --- ## First Deploy ### 1. Clone the repository ```bash git clone git@github.com:aimpress/shumiland-site-dev.git /opt/shumiland cd /opt/shumiland ``` ### 2. Create `.env.production` ```bash cp .env.example .env.production nano .env.production ``` Fill in all values (see table below). Generate secrets with: ```bash openssl rand -base64 32 ``` | Variable | Value | | ---------------------- | -------------------------------------------------------------------- | | `DATABASE_URL` | `postgresql://shumiland:@postgres:5432/shumiland` | | `POSTGRES_PASSWORD` | Generate with openssl | | `PAYLOAD_SECRET` | Generate with openssl | | `REVALIDATE_SECRET` | Generate with openssl | | `SYNC_SECRET` | Generate with openssl | | `CRON_SECRET` | Generate with openssl | | `EZY_PARTNER_KEY` | From ezy.com.ua dashboard | | `EZY_ACTIVITY` | From ezy.com.ua dashboard | | `TELEGRAM_BOT_TOKEN` | From BotFather | | `TELEGRAM_CHAT_ID` | Manager group chat ID | | `RESEND_API_KEY` | From resend.com | | `MANAGER_EMAILS` | Comma-separated manager emails | | `BINOTEL_HMAC_SECRET` | From Binotel dashboard → Webhooks | | `CERTBOT_DOMAIN` | `shumiland.com.ua` | | `CERTBOT_EMAIL` | admin@ai-impress.com | | `NEXT_PUBLIC_SITE_URL` | `https://shumiland.com.ua` | ### 3. Obtain SSL certificate Run certbot once to get the certificate before starting nginx: ```bash # Start only postgres and certbot (nginx needs cert to start) docker compose -f docker-compose.prod.yml run --rm certbot # Verify cert created ls certbot/conf/live/shumiland.com.ua/ ``` ### 4. Pull image and start all services ```bash docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` ### 5. Run database migrations ```bash docker compose -f docker-compose.prod.yml exec app npx payload migrate ``` ### 6. Seed initial data and sync tariffs ```bash # Create admin user + seed globals docker compose -f docker-compose.prod.yml exec app pnpm seed # Sync tariffs from ezy API curl -X POST https://shumiland.com.ua/api/tariffs/sync \ -H "Authorization: Bearer " ``` ### 7. Verify health ```bash curl https://shumiland.com.ua/api/health # Expected: {"status":"healthy","db":"ok","ezy":"ok","ts":...} ``` --- ## GitHub Actions CI/CD ### Required Secrets (Settings → Secrets and variables → Actions) | Secret | Description | | ------------- | -------------------------------------------------- | | `GHCR_TOKEN` | GitHub Personal Access Token with `write:packages` | | `VPS_HOST` | VPS IP address | | `VPS_USER` | SSH username | | `VPS_SSH_KEY` | Private SSH key (`cat ~/.ssh/id_ed25519`) | | `VPS_PORT` | SSH port (default: 22) | ### Deploy Flow Every push to `main`: 1. CI runs lint + typecheck + tests + build 2. Docker image is built and pushed to `ghcr.io/aimpress/shumiland:` 3. SSH into VPS → `docker compose pull && docker compose up -d` --- ## Routine Operations ### Restart services ```bash cd /opt/shumiland docker compose -f docker-compose.prod.yml restart app ``` ### View logs ```bash # App logs (last 100 lines, follow) docker compose -f docker-compose.prod.yml logs -f --tail=100 app # Nginx access logs docker compose -f docker-compose.prod.yml logs -f nginx ``` ### Check service status ```bash docker compose -f docker-compose.prod.yml ps ``` ### Update to latest image manually ```bash cd /opt/shumiland docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` --- ## Database Backup & Restore ### Manual backup ```bash docker compose -f docker-compose.prod.yml exec postgres \ pg_dump -U shumiland shumiland | gzip > /tmp/manual-backup-$(date +%Y%m%d).sql.gz ``` ### Automated backups The `pg_backup` service runs daily at 03:00 UTC. Backups are stored in `./backups/` and older than 14 days are deleted automatically. ```bash # List existing backups ls -lh /opt/shumiland/backups/ ``` ### Restore from backup ```bash # Stop the app (keep postgres running) docker compose -f docker-compose.prod.yml stop app # Restore gunzip -c /opt/shumiland/backups/.sql.gz | \ docker compose -f docker-compose.prod.yml exec -T postgres \ psql -U shumiland shumiland # Start app docker compose -f docker-compose.prod.yml start app ``` --- ## SSL Certificate Renewal Certbot renews automatically via the certbot container. To renew manually: ```bash docker compose -f docker-compose.prod.yml run --rm certbot renew docker compose -f docker-compose.prod.yml restart nginx ``` --- ## Troubleshooting ### App fails to start ```bash # Check logs docker compose -f docker-compose.prod.yml logs app # Common causes: # - Missing .env.production variable → check all required vars are set # - DB not ready → check postgres health: docker compose ps # - Port 3000 conflict → check nothing else runs on port 3000 ``` ### nginx returns 502 Bad Gateway ```bash # Check if app container is running docker compose -f docker-compose.prod.yml ps # Check app logs for startup errors docker compose -f docker-compose.prod.yml logs --tail=50 app ``` ### Database connection error ```bash # Test DB connectivity docker compose -f docker-compose.prod.yml exec postgres \ psql -U shumiland -c "SELECT 1" # Check DATABASE_URL in .env.production matches POSTGRES_PASSWORD ``` ### Tariffs not syncing ```bash # Manual sync curl -X POST https://shumiland.com.ua/api/tariffs/sync \ -H "Authorization: Bearer " -v # Check EZY_ACTIVITY and EZY_PARTNER_KEY are set docker compose -f docker-compose.prod.yml exec app env | grep EZY ```