CI/CD & the 12-Factor App

intermediate ci-cd 12-factor deployment pipeline devops

CI/CD is the automated pipeline that takes our code from a git push to running in production, with as little manual fiddling as possible. In simple language — it’s the assembly line that builds, tests, and ships our code so humans don’t do it by hand (and don’t forget a step at 5pm on a Friday).

The why: manual builds and deploys are slow, inconsistent, and error-prone. Automating them means changes land fast, in small batches, tested the same way every time — so bugs get caught early and a bad deploy is a quick rollback instead of a crisis.

CI vs CD — they’re two different things

The acronym smushes two ideas together. Interviewers like us to split them cleanly.

  • CI (Continuous Integration) — developers merge their work into the shared main branch frequently (many times a day), and every merge automatically triggers a build + test run. The goal: catch integration problems now, while the change is small, instead of during a giant painful merge later.
  • CD — has two flavors people conflate:
    • Continuous Delivery — every change that passes CI is automatically prepared and made deployable; a human clicks the button to actually release.
    • Continuous Deployment — one step further: passing changes go straight to production automatically, no human button.

The only difference between the two CDs is that last manual gate. Delivery = “always ready to ship, we choose when”. Deployment = “if it’s green, it’s live”.

A typical pipeline

A pipeline is a series of stages; if any stage fails, the whole thing stops and nothing ships. Each stage is a gate.

git push → production
BUILD
compile, package image
TEST
unit + integration
SCAN
lint, vulns, secrets
DEPLOY
push to environment
any stage red → pipeline stops, nothing ships
# a stripped-down GitHub Actions pipeline
on: [push]
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci          # build
      - run: npm test        # test
      - run: npm audit       # scan
      - run: ./build.sh      # deploy: build image + push to registry

The build usually produces one immutable artifact (a Docker image) that’s promoted unchanged through staging to production — so what we tested is exactly what runs. No rebuilding per environment.

The 12-Factor App

The twelve-factor app is a set of principles for building services that are easy to deploy, scale, and run in the cloud. We won’t recite all twelve like a shopping list — that’s a red flag in an interview. Instead, here are the ones that actually shape backend design.

  • Config in the environment. Anything that differs between environments (DB URLs, API keys, feature flags) lives in env vars, never hardcoded or committed. Why? One identical build runs everywhere; only the env changes. It also keeps secrets out of git.

    # same image, different env per environment
    DATABASE_URL=postgres://prod-db:5432/app
    LOG_LEVEL=info
  • Stateless processes. Our app processes should keep no important state in local memory or disk between requests. Any state that must persist goes to a backing service (DB, Redis). Why? So we can kill, restart, and scale processes freely — and any instance can handle any request. Store sessions in Redis, not in-process. This is what makes horizontal scaling work.

  • Backing services are attached resources. A database, cache, or queue is just a resource we reach by URL, swappable without a code change. Local Postgres and managed cloud Postgres should be interchangeable by flipping one env var — no code edits.

  • Logs as event streams. The app doesn’t manage log files. It just writes to stdout as a stream, and the platform captures, routes, and stores it. Why? The app shouldn’t care where logs end up — that’s the environment’s job. (This is exactly why structured stdout logging pairs so well with containers.)

  • Dev/prod parity. Keep development, staging, and production as similar as possible — same backing services, same OS, same config shape. Why? The bigger the gap, the more “worked in dev, broke in prod” surprises. Containers help enormously here.

There’s also disposability — start fast, shut down gracefully on SIGTERM — which ties straight into health checks and graceful shutdown.

Interview soundbite

CI is merging to main frequently with automatic build + test on every change; CD is either continuous delivery (always deployable, human clicks release) or continuous deployment (green means live, no human). A pipeline gates build → test → scan → deploy, promoting one immutable artifact through environments. The 12-factor rules that matter most for backend: config in env vars, stateless processes (state in backing services so we scale horizontally), attached backing services, logs to stdout as streams, and dev/prod parity.