On this page
- High-Level Architecture
- Deployment Flow
- Architecture Diagram
- VPS Directory Layout
- GitHub Actions (Example)
- PostgreSQL Setup
- Caddy Reverse Proxy
- Backups
- Monitoring & Troubleshooting
- Orcta Runtime Setup
CI/CD Pipeline
This runbook explains how Orcta deploys services from GitHub to the VPS using GitHub Actions, Docker, and a shared PostgreSQL cluster.
High-Level Architecture
- Codebase: GitHub (
devandmainbranches). - CI/CD: GitHub Actions (SSH deploy to VPS) or Orcta Runtime (webhook-triggered).
- Runtime: Ubuntu VPS running Docker + Docker Compose.
- Deployment Automation: Orcta Runtime
(
runtime.orctatech.com) manages multi-app deployments. - Monitoring: Lookout (server metrics) + LogDeck (log monitoring).
- Services:
- App containers per repo.
- Shared PostgreSQL cluster (Dockerized) with per-app DBs/roles.
- Caddy reverse proxy for routing and TLS.
Deployment Flow
Option 1: Orcta Runtime (Recommended)
- Push to
mainordev. - GitHub fires webhook to
runtime.orctatech.com/webhook. - Orcta Runtime pulls the new image, runs pre-deploy health checks.
- Orcta starts the container with
docker compose up -d. - Health check polling verifies the app is healthy.
- On failure, automatic rollback to last known good version.
Option 2: Direct SSH (Legacy)
- Push to
mainordev. - GitHub Actions connects to the VPS via SSH.
- The repo is cloned or pulled into
/srv/apps/<APP>. - Docker Compose rebuilds and restarts the service.
- Caddy reloads to pick up new routes.
Architecture Diagram
┌───────────────────────┐
│ GitHub Repo │
│ (main & dev branches)│
└───────────┬───────────┘
│
Push triggers
│
▼
┌───────────────────────┐
│ GitHub Actions │
│ (CI/CD Workflows) │
└───────────┬───────────┘
│
SSH Deploys to
│
▼
┌─────────────────────────────────────────┐
│ VPS │
│ (Ubuntu + Docker + Docker Compose) │
│ │
│ ┌───────────────────────────────────┐ │
│ │ Shared Docker Network │ │
│ │ `orcta-net` │ │
│ │ │ │
│ │ ┌─────────────┐ ┌────────────┐ │ │
│ │ │ app1 │ │ app2 │ │ │
│ │ │ dockerized │ │ dockerized │ │ │
│ │ │ service │ │ service │ │ │
│ │ └─────┬───────┘ └──────┬─────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ postgres://app1_db postgres://app2_db
│ │ │ │
│ │ ┌───────────────────┐ │ │
│ │ │ PostgreSQL DB │ │ │
│ │ │ dockerized │ │ │
│ │ │ per-app roles │ │ │
│ │ └───────────────────┘ │ │
│ └───────────────────────────────────┘ │
│ │
│ Backups → /srv/orcta/postgres/backups/ │
└─────────────────────────────────────────┘
VPS Directory Layout
/srv/apps
app1/
docker-compose.yml
Caddyfile-snippet
app2/
docker-compose.yml
Caddyfile-snippet
/srv/orcta-postgres
docker-compose.yml
init/
01-init.sql
backups/
backup.sh
GitHub Actions (Example)
.github/workflows/deploy.yml
name: CI/CD Pipeline (Direct VPS Build)
on:
push:
branches: [dev, main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy on VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
set -e
APP=orcta-app
mkdir -p /srv/apps/$APP
cd /srv/apps/$APP
if [ ! -d ".git" ]; then
git clone git@github.com:Orctatech-Engineering-Team/$APP.git .
else
git pull origin main
fi
docker compose up --build -dPostgreSQL Setup
/srv/orcta-postgres/docker-compose.yml
version: "3.9"
services:
postgres:
image: postgres:16
container_name: orcta_postgres
restart: always
environment:
POSTGRES_PASSWORD: <set-in-env>
volumes:
- ./data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
networks:
- orcta_net
pgbouncer:
image: edoburu/pgbouncer
container_name: orcta_pgbouncer
restart: always
ports:
- "6432:6432"
environment:
DB_USER: pgbouncer
DB_PASSWORD: <set-in-env>
DB_HOST: postgres
DB_PORT: 5432
POOL_MODE: transaction
MAX_CLIENT_CONN: 200
DEFAULT_POOL_SIZE: 20
depends_on:
- postgres
networks:
- orcta_net
networks:
orcta_net:
external: trueExample init script
(/srv/orcta-postgres/init/01-init.sql):
CREATE USER app1_user WITH PASSWORD '<password>';
CREATE DATABASE app1_db OWNER app1_user;
REVOKE ALL ON DATABASE app1_db FROM PUBLIC;Caddy Reverse Proxy
Main /etc/caddy/Caddyfile:
{
email admin@orctatech.com
}
import /srv/apps/*/Caddyfile-snippet
Example snippet:
orcta.example.com {
reverse_proxy localhost:9999
}
Backups
/srv/orcta-postgres/backup.sh:
#!/bin/bash
TIMESTAMP=$(date +%F_%H-%M-%S)
docker exec -t orcta-postgres pg_dumpall -U postgres > /srv/orcta-postgres/backups/db_$TIMESTAMP.sqlCron (2 AM daily):
0 2 * * * /srv/orcta-postgres/backup.sh
Monitoring & Troubleshooting
- App logs:
docker logs -f <container> - Postgres logs:
docker logs -f orcta-postgres - Caddy logs:
journalctl -u caddy --no-pager -n 100 - Orcta Runtime dashboard:
https://runtime.orctatech.com/login - Lookout status:
journalctl -u lookout -f - LogDeck:
https://logdeck.orctatech.com
Orcta Runtime Setup
Orcta Runtime is the deployment automation platform that manages multi-app Docker Compose deploys.
Initial Setup
# On the VPS
git clone git@github.com:Orctatech-Engineering-Team/orcta-runtime.git ~/orcta-runtime
cd ~/orcta-runtime
# Create .env
cp .env.example .env
# Edit .env with:
# AUTH_TOKEN=<your-admin-token>
# WEBHOOK_SECRET=<your-github-webhook-secret>
# Build and start
docker compose build
docker compose up -dRegister an App
# Create an app entry
curl -X POST https://runtime.orctatech.com/api/apps \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"compose_dir": "/srv/apps/my-app",
"health_url": "http://localhost:3000/health",
"timeout_sec": 60
}'Configure GitHub Webhook
In your GitHub repo → Settings → Webhooks → Add webhook:
- Payload URL:
https://runtime.orctatech.com/webhook - Content type:
application/json - Secret: Same as
WEBHOOK_SECRETin.env - Events: Just the push event
Install Monitoring (Lookout)
# Run the bootstrap action via API
curl -X POST https://runtime.orctatech.com/api/execute/bootstrap-monitoring \
-H "Authorization: Bearer <operator-token>"Or install manually:
curl -fsSL https://raw.githubusercontent.com/AmoabaKelvin/lookout/main/install.sh | sudo shLast modified: 2026-08-03