18 KiB
Placebo.mk Deployment Guide (Coolify)
Complete deployment guide for the Placebo.mk satirical news platform on a VPS using Coolify.
Table of Contents
- Architecture Overview
- Prerequisites
- Coolify Setup
- Database Configuration
- Service Deployments
- Environment Variables Reference
- Domain & SSL Configuration
- Push Notifications Setup
- Post-Deployment Checklist
- Troubleshooting
Architecture Overview
┌──────────────────────────────────────┐
│ Coolify Reverse Proxy │
│ (Traefik/Caddy) │
└────────────────┬─────────────────────┘
│
┌────────────────────────────────┼────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ PWA │ │ CMS │
│ (React+Vite) │ │ (React+Vite+SW) │ │ (Strapi) │
│ nginx:80 │ │ nginx:80 │ │ Node.js:1337 │
│ placebo.mk │ │ app.placebo.mk │ │ cms.placebo.mk │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ │ │
│ │ │
└────────────────────────────────┼────────────────────────────────┘
│
▼
┌─────────────────┐
│ Backend │
│ (NestJS) │
│ Node.js:3000 │
│ api.placebo.mk │
└────────┬────────┘
│
▼
┌─────────────────┐
│ PostgreSQL │
│ :5432 │
└─────────────────┘
Tech Stack
| Service | Technology | Port | Purpose |
|---|---|---|---|
| Frontend | React 19 + Vite + Tailwind | 80 | Main website (SSG) |
| PWA | React 19 + Vite + Workbox | 80 | Progressive Web App with push |
| Backend | NestJS + TypeORM | 3000 | REST API |
| CMS | Strapi 5 | 1337 | Content management |
| Database | PostgreSQL 16 | 5432 | Primary data store |
Prerequisites
Server Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 4GB | 8GB |
| CPU | 2 vCPU | 4 vCPU |
| Storage | 40GB SSD | 80GB SSD |
| OS | Ubuntu 22.04 | Ubuntu 24.04 |
Domain Setup
Configure these DNS A records pointing to your VPS IP:
| Type | Name | Purpose |
|---|---|---|
| A | @ |
Main website (frontend) |
| A | www |
WWW redirect |
| A | api |
Backend API |
| A | app |
PWA |
| A | cms |
Strapi admin |
Required Before Deployment
- Coolify installed and accessible
- Domain DNS configured
- GitHub repository connected to Coolify
- SMTP server (optional, for emails)
Coolify Setup
1. Create Project
- Login to Coolify dashboard
- Navigate to Projects
- Click + New Project
- Name:
placebo - Click Create
2. Create Environment
- Inside
placeboproject - Click + New Environment
- Name:
production - Click Create
3. Connect Repository
- Go to Configuration → Source
- Click Connect GitHub
- Authorize Coolify to access your repositories
- Select repository:
your-org/placeboMk
Quick Deploy (Docker Compose)
Deploy all services in one step using Docker Compose.
Step 1: Create Service
- In
productionenvironment, click + New Resource - Select Docker Compose (not Dockerfile)
- Name:
placebo-stack - Repository: Select your connected repo
- Compose File:
docker-compose.coolify.yml
Step 2: Set Environment Variables
Click Environment Variables and add these secrets:
| Variable | How to Generate |
|---|---|
DATABASE_PASSWORD |
openssl rand -base64 32 |
JWT_SECRET |
openssl rand -base64 64 | tr -d '\n' |
VAPID_PUBLIC_KEY |
Run npm run generate-vapid locally |
VAPID_PRIVATE_KEY |
Run npm run generate-vapid locally |
STRAPI_APP_KEYS |
See generation command below |
STRAPI_API_TOKEN_SALT |
openssl rand -base64 32 |
STRAPI_ADMIN_JWT_SECRET |
openssl rand -base64 32 |
STRAPI_TRANSFER_TOKEN_SALT |
openssl rand -base64 32 |
STRAPI_JWT_SECRET |
openssl rand -base64 32 |
STRAPI_API_TOKEN |
Get from CMS admin after first deploy |
Generate Strapi APP_KEYS (4 comma-separated keys):
for i in {1..4}; do node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"; done | paste -sd "," -
Generate VAPID Keys (run locally):
cd backend
npm install
npm run generate-vapid
Step 3: Configure Domains
The compose file uses Traefik labels with these domains:
placebo.mk- Frontendwww.placebo.mk- Frontend (redirect)api.placebo.mk- Backend APIapp.placebo.mk- PWAcms.placebo.mk- CMS (Strapi)
Ensure DNS is configured before deploying.
Step 4: Deploy
- Click Deploy
- Wait for all services to start (2-5 minutes)
- Check logs if any service fails
Step 5: Create CMS Admin
- Visit
https://cms.placebo.mk/admin - Create your admin user
- Generate API token for backend
- Add
STRAPI_API_TOKENto environment variables - Redeploy
Manual Deploy (Individual Services)
If you prefer deploying each service separately, follow these steps.
Database Configuration
Create PostgreSQL Service
- In
productionenvironment, click + New Resource - Select Database → PostgreSQL
- Configure:
| Setting | Value |
|---|---|
| Name | placebo-db |
| Version | 16-alpine |
| PostgreSQL User | placebo_user |
| PostgreSQL Password | <generate-secure-password> |
| PostgreSQL Database | placebo_db |
| Persistent Volume | Enabled |
- Click **Deploy`
Note Connection Details
After deployment, note the internal hostname:
Host: placebo-db
Port: 5432
Database: placebo_db
Username: placebo_user
Password: <your-password>
Service Deployments (Manual)
1. Backend (NestJS API)
Create Service
- Click + New Resource → Service
- Select Dockerfile (not Docker Compose)
- Name:
placebo-backend - Repository: Select your connected repo
Configuration
| Setting | Value |
|---|---|
| Dockerfile Location | backend/Dockerfile |
| Build Context | backend |
| Port | 3000 |
Environment Variables
NODE_ENV=production
PORT=3000
# Database
DATABASE_TYPE=postgres
DATABASE_HOST=placebo-db
DATABASE_PORT=5432
DATABASE_USERNAME=placebo_user
DATABASE_PASSWORD=<your-db-password>
DATABASE_NAME=placebo_db
DATABASE_SYNCHRONIZE=false
DATABASE_LOGGING=false
# JWT Authentication
JWT_SECRET=<generate-64-char-secret>
JWT_EXPIRATION=86400
# CORS
CORS_ORIGIN=https://placebo.mk,https://www.placebo.mk,https://app.placebo.mk
# Strapi CMS
STRAPI_URL=https://cms.placebo.mk
STRAPI_API_TOKEN=<your-strapi-api-token>
# Push Notifications
VAPID_SUBJECT=mailto:contact@placebo.mk
VAPID_PUBLIC_KEY=<generated-public-key>
VAPID_PRIVATE_KEY=<generated-private-key>
Generate Secrets
JWT Secret:
openssl rand -base64 64 | tr -d '\n'
VAPID Keys (run locally):
cd backend
npm install
npm run generate-vapid
Health Check
| Setting | Value |
|---|---|
| Path | /health |
| Interval | 30s |
| Timeout | 10s |
| Retries | 3 |
Domain
- Go to Domains tab
- Add:
api.placebo.mk - Enable HTTPS (Let's Encrypt)
2. Frontend (Main Website)
Create Service
- Click + New Resource → Service → Dockerfile
- Name:
placebo-frontend
Configuration
| Setting | Value |
|---|---|
| Dockerfile Location | frontend/Dockerfile |
| Build Context | frontend |
| Port | 80 |
Build Arguments
Vite requires environment variables at build time. Add these as Build Args:
VITE_API_URL=https://api.placebo.mk/api/v1
VITE_CMS_URL=https://cms.placebo.mk
Domain
- Add:
placebo.mk - Add:
www.placebo.mk - Enable HTTPS
3. PWA (Progressive Web App)
Create Service
- Click + New Resource → Service → Dockerfile
- Name:
placebo-pwa
Configuration
| Setting | Value |
|---|---|
| Dockerfile Location | pwa/Dockerfile |
| Build Context | pwa |
| Port | 80 |
Build Arguments
VITE_API_URL=https://api.placebo.mk/api/v1
VITE_CMS_URL=https://cms.placebo.mk
Persistent Volume (Optional)
For service worker and cache:
- Container Path:
/usr/share/nginx/html - Host Path:
pwa-dist
Domain
- Add:
app.placebo.mk - Enable HTTPS
4. CMS (Strapi)
Create Service
- Click + New Resource → Service → Dockerfile
- Name:
placebo-cms
Configuration
| Setting | Value |
|---|---|
| Dockerfile Location | cms/cms/Dockerfile |
| Build Context | cms/cms |
| Port | 1337 |
Environment Variables
NODE_ENV=production
HOST=0.0.0.0
PORT=1337
# Database
DATABASE_CLIENT=postgres
DATABASE_HOST=placebo-db
DATABASE_PORT=5432
DATABASE_NAME=placebo_db
DATABASE_USERNAME=placebo_user
DATABASE_PASSWORD=<your-db-password>
DATABASE_SSL=false
# Security Keys (generate unique values for each)
APP_KEYS=<generate-4-keys>
API_TOKEN_SALT=<32-char-secret>
ADMIN_JWT_SECRET=<32-char-secret>
TRANSFER_TOKEN_SALT=<32-char-secret>
JWT_SECRET=<32-char-secret>
# CORS
CORS_ORIGIN=https://placebo.mk,https://app.placebo.mk,https://api.placebo.mk
Generate Strapi Keys
For APP_KEYS (4 comma-separated keys):
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Run 4 times, join with commas
For other secrets:
openssl rand -base64 32
Persistent Volumes
Add these volumes for data persistence:
| Container Path | Purpose |
|---|---|
/app/public/uploads |
Media uploads |
/app/.tmp |
Temporary files |
Domain
- Add:
cms.placebo.mk - Enable HTTPS
Environment Variables Reference
Backend
| Variable | Required | Description |
|---|---|---|
NODE_ENV |
Yes | Set to production |
DATABASE_HOST |
Yes | PostgreSQL hostname |
DATABASE_PORT |
Yes | PostgreSQL port (5432) |
DATABASE_USERNAME |
Yes | Database username |
DATABASE_PASSWORD |
Yes | Database password |
DATABASE_NAME |
Yes | Database name |
DATABASE_SYNCHRONIZE |
Yes | Set to false in production |
JWT_SECRET |
Yes | 64+ character secret |
CORS_ORIGIN |
Yes | Comma-separated allowed origins |
VAPID_PUBLIC_KEY |
Yes | For push notifications |
VAPID_PRIVATE_KEY |
Yes | For push notifications |
STRAPI_URL |
Yes | CMS URL |
STRAPI_API_TOKEN |
Yes | API token from Strapi |
Frontend/PWA (Build Args)
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
Yes | Backend API URL |
VITE_CMS_URL |
Yes | CMS URL |
CMS (Strapi)
| Variable | Required | Description |
|---|---|---|
APP_KEYS |
Yes | 4 comma-separated 32-byte keys |
API_TOKEN_SALT |
Yes | Random 32-byte string |
ADMIN_JWT_SECRET |
Yes | Random 32-byte string |
JWT_SECRET |
Yes | Random 32-byte string |
TRANSFER_TOKEN_SALT |
Yes | Random 32-byte string |
Domain & SSL Configuration
Configure Each Service
For each deployed service:
- Go to service → Configuration → Domains
- Click Add Domain
- Enter domain (e.g.,
api.placebo.mk) - Enable HTTPS
- Select Let's Encrypt
- Click Request Certificate
SSL Certificate Generation
Coolify automatically handles SSL via Let's Encrypt. Ensure:
- DNS is properly configured
- Port 80 and 443 are open
- Domain is accessible from internet
Push Notifications Setup
1. Generate VAPID Keys
Run locally in the backend directory:
cd backend
npm install
npm run generate-vapid
Output:
Generating VAPID keys...
VAPID keys generated and added to .env file:
Public Key: BIAna-ulT88Ek5ZdiAiXJikks3T5JEuZjBstuO7bEbUJ6RAoSmXAbum1Pf7JIbo-YQfwXM1Yi-rGTttOuNJUpUE
Private Key: DGtRu68SmAxng-xx3BiTCa9NrztQbsSoECRuzry9VD4
2. Configure Backend
Add to backend environment variables:
VAPID_SUBJECT=mailto:contact@placebo.mk
VAPID_PUBLIC_KEY=<your-public-key>
VAPID_PRIVATE_KEY=<your-private-key>
3. Verify Setup
- Deploy backend with VAPID keys
- Visit:
https://api.placebo.mk/api/v1/push/vapid-public-key - Should return JSON with your public key
4. Test in PWA
- Open
https://app.placebo.mk - Wait for notification banner
- Click "Вклучи" (Enable)
- Accept browser permission
- Verify subscription in admin dashboard
Post-Deployment Checklist
Backend
- Health check passing:
https://api.placebo.mk/health - API docs accessible:
https://api.placebo.mk/api/v1 - Database connected (check logs)
- JWT authentication working
- CORS accepting frontend origins
- Push notification endpoint working
Frontend
- Site loads:
https://placebo.mk - No console errors
- API calls successful (check Network tab)
- Images and assets loading
- Navigation working correctly
- Admin dashboard accessible
PWA
- Site loads:
https://app.placebo.mk - Service worker registered (DevTools → Application)
- Install prompt appears
- Notification banner shows
- Push subscription successful
- Works offline (cached resources)
CMS
- Admin panel:
https://cms.placebo.mk/admin - First admin user created
- API token generated for backend
- Media uploads working
- Content types created
Troubleshooting
Backend Won't Start
Symptoms: Container keeps restarting
Solutions:
- Check logs: Coolify → Service → Logs
- Verify database connection:
DATABASE_HOSTshould be service name (e.g.,placebo-db)- Check password matches database password
- Verify all required env vars are set
- Check JWT_SECRET is at least 32 characters
Frontend Shows Blank Page
Symptoms: Page loads but nothing displays
Solutions:
- Check browser console for errors
- Verify build args were set:
VITE_API_URLmust be set at build time
- Check Network tab for failed API calls
- Rebuild with correct environment variables
CORS Errors
Symptoms: Browser blocks API requests
Solutions:
- Add all frontend domains to
CORS_ORIGIN:CORS_ORIGIN=https://placebo.mk,https://www.placebo.mk,https://app.placebo.mk - Restart backend after changing
- Clear browser cache
Push Notifications Not Working
Symptoms: Subscription fails or notifications don't arrive
Solutions:
- Verify HTTPS is enabled (required for push)
- Check VAPID keys are set in backend
- Test endpoint:
/api/v1/push/vapid-public-key - Check service worker is registered
- Verify browser notification permission is granted
Database Connection Issues
Symptoms: Backend can't connect to database
Solutions:
- Verify database service is running
- Check hostname matches database service name
- Verify credentials are correct
- Ensure both services are on the same network
- Check database logs for errors
CMS Admin Not Accessible
Symptoms: 404 or redirect loop on /admin
Solutions:
- Verify all Strapi secrets are set
- Check
HOST=0.0.0.0is set - Clear browser cache and cookies
- Check CMS logs for startup errors
Maintenance
Updating Deployments
- Push changes to GitHub main branch
- Coolify → Service → Redeploy
- Or enable automatic deployments on push
Database Backups
- Coolify → Database → Backups
- Enable scheduled backups
- Recommended retention: 7-30 days
- Store backups off-server periodically
Log Management
- View logs: Coolify → Service → Logs
- Download logs: Available in service settings
- Log rotation: Configured automatically
Monitoring
- Set up Coolify notifications for:
- Service down
- High resource usage
- Deployment failures
- Monitor disk usage (uploads, logs)
- Check SSL certificate expiration
Security Checklist
- All secrets are 32+ characters
- HTTPS enabled on all services
- Database not exposed to internet
- CORS restricted to your domains only
- CMS admin protected (consider IP whitelist)
- Regular security updates applied
- Backups configured and tested
- Monitoring and alerts enabled
Support
For issues specific to:
- Coolify: Coolify Documentation
- NestJS: NestJS Documentation
- Strapi: Strapi Documentation
- Vite/PWA: Vite PWA Plugin