Continuous Deployment Without Cloud Lock-In: 2025 Best Practices for Open DevOps Teams

Automate CD with Git, Buildbot and container runners you control. This guide shows how to design a vendor‑neutral pipeline that ships fast without handing your keys to a hosted platform. Everything here works on one well‑tuned VPS or across a few bare‑metal nodes and scales up later with minimal rework.

Pipeline at a glance

  • Git as the single source of truth: trunk‑based flow with short‑lived PRs.
  • Buildbot as CI/CD coordinator (masters + containerized workers).
  • Self‑hosted registry (e.g., registry:2 or Harbor) for immutable images.
  • Edge proxy (Nginx/Caddy) for blue‑green or rolling switchovers.
  • CMMS‑lite (plain Git logs + CHANGELOG + status page) for auditability.

Environments without the cloud tax

You do not need managed environments to keep risk low. On a single VPS, use namespaces or Compose projects to isolate stacks:

# two Compose projects on one host
docker compose -p app_staging -f compose.yml up -d
docker compose -p app_prod    -f compose.yml up -d

Across bare‑metal nodes, group them by role (edge, workers, db). Buildbot targets hosts by label, so the same pipeline can deploy to staging first, then promote to production by retagging the image.

Zero‑downtime rollouts

Prefer blue‑green when you can duplicate the stack; choose rolling when capacity is tight.

# Nginx upstream switch (blue -> green)
upstream app_blue  { server 127.0.0.1:5001; }
upstream app_green { server 127.0.0.1:5002; }
map $deployment $app_upstream { default app_blue; green app_green; }
server {
  listen 443 ssl http2;
  location / { proxy_pass http://$app_upstream; }
}
# flip by reloading with: deployment=green

For rolling, run two replicas and restart one at a time with health checks. Keep migrations backward‑compatible through at least one release so you can roll back safely.

CD with Buildbot (minimal sketch)

from buildbot.plugins import schedulers, util, steps, worker
c = BuildmasterConfig = {}
c['workers'] = [worker.DockerLatentWorker('cdw','unix:///var/run/docker.sock', image='runner:latest')]
c['schedulers'] = [
  schedulers.SingleBranchScheduler(name='main', change_filter=util.ChangeFilter(branch='main'), builderNames=['cd'])
]
factory = util.BuildFactory([
  steps.Git(repourl='https://code.exana.io/diffusion/APP/app.git'),
  steps.ShellCommand(command=['make','test']),
  steps.ShellCommand(command=['docker','build','-t','registry.exana.io/app:${revision}','.']),
  steps.ShellCommand(command=['docker','push','registry.exana.io/app:${revision}']),
  steps.ShellCommand(name='deploy-staging', command=['./ops/deploy.sh','staging','${revision}']),
  steps.ShellCommand(name='smoke', command=['./ops/smoke.sh','staging']),
  steps.ShellCommand(name='promote', command=['./ops/deploy.sh','prod','${revision}'])
])
c['builders'] = [util.BuilderConfig(name='cd', workernames=['cdw'], factory=factory)]

This keeps logic in scripts stored with the app (ops/deploy.sh), so developers can change rollout tactics without touching CI internals.

Secrets without vendor lock‑in

Use sops (age/GPG) or git‑crypt to keep encrypted files in‑repo and decrypt on workers with short‑lived keys. Avoid mounting the host Docker socket into app containers; pass secrets as files with tight permissions.

# decrypt at deploy time
sops -d deploy/prod.env.enc > /run/secrets/prod.env
export $(grep -v '^#' /run/secrets/prod.env | xargs -0 -I{{}} echo {{}})

Database migrations & contracts

  • Expand‑migrate‑contract: add nullable columns or new tables first; deploy code that reads both; run backfills; only then drop deprecated fields.
  • Idempotent migrations: safe to rerun during retries.
  • Feature flags: route risky changes behind toggles so you can disable without redeploying.

Artifacts and registry you own

Push images to a self‑hosted registry with immutable tags and signed manifests. Keep a cleanup job that prunes unreferenced layers but retains the last N releases for instant rollback.

Observability & rollback

  • Golden signals: latency, error rate, saturation, traffic. Alert on symptoms, not internals.
  • Health probes: HTTP 2xx + /ready endpoints that verify dependencies.
  • Rollback patterns: (1) flip upstream back to blue; (2) redeploy previous image digest; (3) toggle off features; (4) if DB change caused impact, restore to the safe point snapshot.

Security & supply chain

  • Pin base images by digest and scan during build; fail the pipeline on critical CVEs.
  • Sign images (cosign) and verify before deploy.
  • Run containers as non‑root; drop capabilities; read‑only rootfs where possible.

A simple deploy script

#!/usr/bin/env bash
set -euo pipefail
ENV="$1"; REV="$2"
STACK="app_${ENV}"
docker pull registry.exana.io/app:${REV}
# Update image and perform rolling restart
docker compose -p "${STACK}" -f compose.yml up -d --no-deps --scale web=2 --build web
# Smoke check
curl -fsS https://app-${ENV}.exana.io/ready > /dev/null

Takeaways

Continuous delivery without cloud lock‑in is about discipline, not vendors. Keep configuration in Git, images in your registry, secrets encrypted, and rollout logic in scripts you own. With Buildbot orchestrating tests, image builds and blue‑green or rolling switches, teams deploy confidently on a single VPS today and scale to multiple nodes tomorrow—no rewrites, no surprise plan changes.