CI/CD turns “it works on my machine” into an automated, repeatable path from a code change to production. Every push is built and tested automatically; every successful build can be deployed with confidence — often without anyone logging into a server.
The terms
- Continuous Integration (CI) — every change is merged frequently into a shared branch and automatically built and tested. Problems surface within minutes, not at release time.
- Continuous Delivery — every change that passes CI is ready to deploy; a human clicks the button for production.
- Continuous Deployment — every change that passes is deployed to production automatically.
Most small teams aim for continuous delivery: automatic deploys to staging, one click (or merge) for production.
A typical pipeline
push / pull request
│
▼
1. Checkout & install dependencies (cached)
2. Lint & static checks (formatting, types, security scanning)
3. Unit tests
4. Build artifact (bundle, binary, Docker image)
5. Integration / end-to-end tests
6. Publish artifact (registry, artifact store)
│
▼ (main branch)
7. Deploy to staging → smoke tests
8. Deploy to production (manual approval or automatic)
9. Post-deploy checks, notifications
Build once, deploy many: the same artifact (e.g. a Docker image tagged with the commit SHA) moves from staging to production. Rebuilding for each environment risks shipping something you didn’t test.
Tools
| Tool | Hosting | Notes |
|---|---|---|
| GitHub Actions | GitHub, self-hosted runners | Huge marketplace of actions |
| GitLab CI/CD | GitLab.com or self-hosted | Integrated with GitLab |
| Gitea / Forgejo Actions | Self-hosted | GitHub Actions–compatible syntax |
| Jenkins | Self-hosted | Extremely flexible, more maintenance |
| Woodpecker / Drone | Self-hosted | Lightweight, container-based |
Guides: GitHub Actions deploy to VPS, GitLab CI over SSH, Jenkins, Gitea Actions, Woodpecker CI.
Deploying to a VPS
Common patterns, from simplest to most robust:
- SSH + git pull — the pipeline SSHes in and runs a deploy script. Simple; the server needs build tools.
- Build in CI, rsync artifacts — the server only receives built files. See deploying a React app.
- Build a Docker image, push to a registry, pull on the server — consistent environments; easy rollbacks by image tag.
- GitOps — the pipeline updates manifests in Git; an agent in the cluster applies them. See Argo CD.
Make deployments atomic and reversible: release directories with symlink switches, image tags per commit, and a documented rollback command. See deployment strategies and zero-downtime Node.js deploys.
Secrets in pipelines
- Store credentials in the CI system’s secret store, scoped to protected branches/environments.
- Use dedicated, least-privilege deploy users and keys — see sudo for automation.
- Never print secrets in logs; mask them.
- Verify SSH host keys (
known_hosts) instead of disabling checks.
Keeping pipelines fast
Slow pipelines get skipped. Keep feedback under ~10 minutes:
- Cache dependencies (npm, pip, Composer, Go modules) and Docker layers — see Docker build cache.
- Run jobs in parallel (lint, unit tests, build).
- Only run what changed in monorepos (path filters).
- Use faster runners: a self-hosted runner on a VPS with warm caches often beats shared cloud runners. See self-hosted GitHub runner and GitLab Runner.
- Fail fast: cheap checks first.
Good practices
- Protect the main branch: required checks must pass before merging.
- Small, frequent changes — easier to test, review and roll back.
- Tests you trust: flaky tests erode confidence in the whole pipeline.
- Deploy outside peak hours until you trust the process; then deploy any time.
- Monitor after deploys and alert on errors — see Uptime Kuma.
Getting started
- Add a CI workflow that installs dependencies and runs tests on every push.
- Add a build step that produces an artifact.
- Add a manual deploy job for staging, then production.
- Automate staging deploys; keep a manual gate for production until you’re comfortable.
A small KVM VPS makes a capable self-hosted runner, keeping build caches warm and builds fast.