How I build CI/CD systems that are testable, traceable, and safe to promote.
Every pull request proves itself in real infrastructure. Every release is versioned and tagged. Every deployment is reversible.
The foundation of reliable delivery
A pull request is where infrastructure, build, deployment, and testing happen in a dedicated test environment.
Mocked environments hide real problems. Validate in a real isolated environment before touching shared ones.
Merging to main means the change is validated. main represents code safe to deploy.
CI proves the change. CD promotes trusted versions. They work together, not merged into chaos.
Every deployment maps to a clear version tag. If you cannot tell what version is running where, your pipeline is failing.
Promote using versioned artifacts and tags, not rebuilds. The same tested version moves forward.
Every deployment strategy is incomplete without rollback. Every release should be reversible.
From feature branch to production
Developer creates a feature branch for a change.
Terraform provisions isolated infrastructure. GitHub Actions builds and deploys. Tests run against real env.
After approval, merge triggers CI pipeline. Build once, deploy to dev, verify merged state.
CD creates version tag (v1.2.0-dev). This tag is the release identity.
Same Docker image → staging, prod. Only secrets/config change per environment.
Deployment fails? Rollback by version tag. Fast, explicit, reversible.
Ephemeral per-PR environments
Recommended
Create isolated PR environments using Terraform on Google Cloud.
Why
Isolation means no flaky results, no coordination overhead, and real proof that the change works before it touches main.
Same artifact, different environment config
Build once. Version it. Promote same Docker image across environments:
v1.2.0-dev → deploy to Kubernetes dev namespace v1.2.0-rc → release candidate in staging v1.2.0 → production release Same image. Secrets/config injected per environment.
This ensures traceability, prevents rebuild drift, and keeps secrets isolated from the artifact.
Keep it simple: main to tag to deploy
Merge to Main
PR validated and approved. All tests pass. Merge to main—main is always production-ready.
Create Semantic Tag
Tag main with version (v1.2.3). GitHub Actions auto-generates release notes by diffing against previous tag.
Deploy from Tag
Trigger deployment via webhook or manual approval. Full traceability: every deploy tied to a commit and version.
No release branch. No staging merge. Just:
main → semantic tag → GitHub Actions release notes → deploy
Clean, traceable, automatic. Main is production. Tag is history. Diff between tags tells the story of what changed.
Anti-patterns that break reliability
GitHub + GitHub Actions + Terraform + Next.js + Google Cloud
GitHub → Source of truth, PR workflow GitHub Actions → CI/CD orchestration Terraform → Infrastructure as code Docker → Build once, deploy everywhere Kubernetes → Scale and manage containers Google Cloud → Compute, storage, services
Build and Deploy
GitHub Actions builds your app once. Docker packages it. Kubernetes runs it. Same image, multiple environments.
Infrastructure
Terraform provisions ephemeral per-PR environments on Google Cloud. Easy, repeatable, disposable.
Docker + Kubernetes pattern: Build once with GitHub Actions, package in Docker, deploy same image across environments via Kubernetes. Secrets and config injected per environment.
PR opened ↓ Terraform provisions isolated GKE namespace ↓ GitHub Actions builds and tags Docker image ↓ Deploy to pr-123.example.com for testing ↓ Merge to main ↓ Build Docker image → push with tag v1.2.0-dev ↓ Deploy to Kubernetes dev namespace ↓ CD promotes same image to staging, prod ↓ Only secrets/config changes per environment ↓ Rollback: kubectl rollout undo + version tag
Changes proven in isolated real infrastructure. Releases versioned and tagged. Deployments traceable and reversible.