Most small teams do not need Kubernetes. They need reliable deploys that work on a single VPS, with backups they trust and rollbacks that take seconds. A common pattern is Docker Compose with Postgres, Redis, CMS, and Next.js in standalone mode on a single server, deployed via GitHub Actions over SSH.
This guide covers that pipeline for production use.
1. Architecture that fits small teams
One host runs Postgres 16 with pgvector for search, Redis for cache and queues, CMS backend, Next.js frontend, and Caddy or Traefik for TLS. Files go to S3-compatible storage with daily lifecycle to cold archive. Cloudflare sits in front for DNS, CDN, and WAF.
Why not Kubernetes. K8s adds cluster upgrades, networking, and cost that a two-developer team cannot afford. Compose files are readable, diffable in git, and restorable by any freelancer. When you outgrow one host at around 50k daily requests, split the database first, not the orchestrator.
For Next.js, build with output standalone to keep images under 200MB. For CMS, pin minor versions and snapshot schema in git on every change. For Postgres, enable WAL archiving to S3 from day one.
2. GitHub Actions pipeline step by step
Pipeline stages are lint, typecheck, unit tests, build, push image, then deploy. Lint must include no-floating-promises for payments. Typecheck must be strict. Tests must cover VAT rounding, webhook parsing, and RTL helpers. Build must use layer caching for pnpm or npm.
Deploy job connects via SSH with a restricted deploy key, pulls the new image tag like sha plus date, runs migrations with backup first, then restarts services one by one with healthchecks. Healthcheck hits /api/health that checks DB, Redis, and disk. If health fails twice, the job restores the previous tag automatically.
Keep two images on the host at all times for instant rollback. Tag scheme is latest plus git sha. Rollback is switching the Compose tag and restarting, under 30 seconds.
3. Database migrations without downtime
Store CMS schema snapshots plus SQL migrations in git. Before deploy, pg_dump to S3 with timestamp. Apply migrations with statement timeouts and lock timeouts to avoid table locks during Saudi business hours. For risky changes like adding NOT NULL to orders, do expand then backfill then constrain across two deploys.
Test restore monthly on a staging host. An untested backup is not a backup. Document restore steps in Arabic and English so on-call can follow at 2am.
4. Backups, monitoring, and secrets
Daily encrypted backups of Postgres plus file storage to S3 with 30-day retention and 6-year archive for Fatoora invoices. Redis persistence with AOF. Secrets in GitHub Environments plus host vault, never in Compose files. Rotate CMS admin passwords and API tokens quarterly.
Monitor disk above 80 percent, memory, cert expiry under 14 days, and ZATCA queue age over 12 hours. Uptime checks from Dammam and Riyadh, not just EU. Alert via WhatsApp plus email for Arabic on-call.
5. Zero-downtime deploys on one host
Trick is restart app containers while Postgres and Redis stay up, plus Cloudflare retry on 502 for 5 seconds. Deploy at low-traffic windows like 4am Riyadh time. Announce maintenance in Arabic on status page only when DB migration needs exclusive lock.
Bottom line is boring and restorable beats fancy. One Compose file, one pipeline, two images, tested restores, and Arabic runbooks. That is DevOps for small teams.





