VPS container deployment
This guide explains how to install and run the complete Payofy stack on a VPS using Docker and a single shell script.
System prerequisites
Before running the installation script on your server, make sure your VPS has:
- Ubuntu 22.04 LTS or Debian 12 (recommended).
- Docker engine version 24 or newer.
- Docker Compose plugin v2.
- Git.
- At least 2 GB RAM and 20 GB free disk space.
Architecture on the VPS
The VPS runs eight isolated containers on a private Docker bridge network:
payofy-postgres-1: PostgreSQL 16 database storing tenants, merchants, and transactions.payofy-redis-1: Redis 7 cache and job queue broker.payofy-api-1: REST API running Fastify on port 3000.payofy-worker-1: Background job worker processing queue tasks.payofy-admin-1: Staff operations web console.payofy-portal-1: Partner and merchant web portal.payofy-docs-1: Developer documentation portal.payofy-gateway-1: Nginx reverse proxy routing traffic on port 80.
Installation steps
Run these commands in your VPS terminal.
1. Clone the repository
git clone https://github.com/spicyfy/payofy.git
cd payofy
2. Configure environment variables
Copy the sample environment file and enter your secrets:
cp .env.example .env
nano .env
Set your database password, Redis credentials, and Kurv API keys.
3. Run the installer script
You can deploy directly to your VPS from your local computer by providing your VPS IP:
# From your computer (automatically uses username kandikova, port 31973, and ~/.ssh/id_ed25519)
./scripts/deploy/vps-install.sh "app.payofy.si"
# Or with an IP address:
./scripts/deploy/vps-install.sh "198.51.100.1"
If you are already logged in to the VPS terminal:
# Directly on the VPS terminal
./scripts/deploy/vps-install.sh "198.51.100.1"
The script automatically:
- Tests SSH connectivity and verifies or installs Docker engine, Node.js, and PM2.
- Syncs the codebase to
~/payofyon the remote server. - Builds Docker images for all applications.
- Starts PostgreSQL, Redis, backend services, web consoles, and the Nginx gateway.
- Waits for database readiness.
- Runs database migrations.
- Seeds initial tenant and merchant records.
- Configures PM2 log streaming for all services.
- Prints service health checks and active endpoints.
Service endpoints
When the script finishes, your services are reachable at these URLs:
- Nginx Gateway:
http://<your-vps-ip>/andhttps://<your-vps-ip>/ - API Root:
https://api.payofy.si/v1/health(orhttp://api.payofy.si/v1/health) - Admin Console:
https://admin.payofy.si(orhttp://admin.payofy.si) - Partner Portal:
https://portal.payofy.si(orhttp://portal.payofy.si) - Documentation:
https://docs.payofy.si(orhttp://docs.payofy.si)
SSL and HTTPS configuration
Payofy supports free Let's Encrypt SSL certificates for HTTPS access on port 443.
DNS verification
Before issuing certificates, make sure your domain A records resolve to your VPS:
api.payofy.sipoints to your VPS IPadmin.payofy.sipoints to your VPS IPdocs.payofy.sipoints to your VPS IPportal.payofy.sipoints to your VPS IPapp.payofy.sipoints to your VPS IP
Running the SSL setup script
Run this script on the VPS terminal:
./scripts/setup/ssl-setup.sh admin@payofy.si
The script:
- Verifies that each domain resolves to the VPS IP to avoid Let's Encrypt challenge errors.
- Issues a certificate via Certbot using HTTP webroot mode without stopping containers.
- Installs the certificates into
./docker/ssl/. - Gracefully reloads the Nginx gateway.
Portal security and authentication
Both the Ops Admin Console and Partner Portal include in-app authentication, session protection, and gateway-level security headers.
Default credentials
- Ops Admin Console (
https://admin.payofy.si):- Email:
admin@payofy.si - Passcode:
PayofyStaff2026! - Security: 5 failed attempts trigger a 60-second lockout. Sessions expire upon closing the browser window.
- Email:
- Partner Portal (
https://portal.payofy.si):- Email option:
partner@budofy.si/BudofyPartner2026! - API Key option: Accepts any tenant secret key starting with
pfy_live_orpfy_test_.
- Email option:
Gateway security protections
The Nginx gateway enforces the following settings:
- Frame protection:
X-Frame-Options: SAMEORIGINblocks iframe clickjacking. - MIME protection:
X-Content-Type-Options: nosniffstops MIME type sniffing. - Transport security:
Strict-Transport-Securityenforces HTTPS across all subdomains. - Rate limiting: 25 requests per second on portal routes and 30 requests per second on API routes.
- Version hiding:
server_tokens offprevents exposing Nginx version numbers.
Checking logs with PM2
PM2 runs on the VPS host to stream and manage logs across all containers.
Common PM2 log commands
View live logs from all services at once:
pm2 logs
Stream logs from a specific service:
# REST API logs
pm2 logs payofy-api
# Background queue worker logs
pm2 logs payofy-worker
# Nginx reverse proxy logs
pm2 logs payofy-gateway
# Database logs
pm2 logs payofy-postgres
# Redis cache logs
pm2 logs payofy-redis
Display only the last 50 lines without continuous streaming:
pm2 logs payofy-api --lines 50 --nostream
Check status and memory usage of all log streams:
pm2 status
Open the interactive live terminal dashboard:
pm2 monit
Clear historical log files:
pm2 flush
Starting and stopping PM2
Start all log stream processes:
pm2 start ecosystem.config.js
Restart log streamers:
pm2 restart ecosystem.config.js
Stop log streamers:
pm2 stop ecosystem.config.js
You can also use the ops script from the repository root:
./scripts/ops/logs.sh api
./scripts/ops/logs.sh worker
./scripts/ops/logs.sh monit
Docker operations commands
Check running containers:
docker compose ps
Restart all services:
docker compose restart
Stop all services:
docker compose down