The pipeline as the only path
One rule made everything else work: nothing reaches production except through the pipeline. No SSH hotfixes, no manually copied DLLs, no exceptions for emergencies — especially not for emergencies. When the pipeline is the only path, every deploy is reproducible, auditable, and revertible; the 2 a.m. fix goes through the same tests as the Tuesday feature.
What every service’s pipeline runs
- Build & unit tests: fast feedback under 5 minutes — slower than that and developers stop waiting for it.
- Static analysis & dependency scanning: vulnerabilities blocked at PR time cost minutes; found in production they cost audits.
- Contract tests: OCPI/OCPP payload compatibility verified against recorded partner interactions — the protocol seam is where regressions hide.
- Deploy to staging, smoke, promote: the same image, environment config injected — what was tested is what ships.
Deployment strategies by risk
- Stateless APIs: rolling replacement with instant rollback — deploys are non-events.
- Billing and CDR services: blue-green with a manual gate and a verification window — the money path earns extra ceremony.
- Charger-connection services: drain-and-shift — existing OCPP WebSocket connections migrate gracefully, never dropped mid-session.
- Database migrations: expand-and-contract, always backward compatible one version — schema and code deploy independently or rollback is a lie.
The cultural part nobody budgets for
CI/CD fails as a tooling project and succeeds as a habit change. Small PRs had to become normal, broken builds had to become everyone’s immediate problem, and “who approved this deploy” had to become “the pipeline did, here’s the evidence.” That took a year of consistent leadership; the YAML took a week.
Worked example: expand-and-contract migration
The discipline that makes rollback real: schema and code deploy independently, each version compatible one step back:
-- Deploy N: EXPAND - add nullable column, backfill async
ALTER TABLE charging_session ADD stop_reason VARCHAR(32) NULL;
-- code version N writes both old and new columns
-- Deploy N+1: code reads new column, falls back to old
-- (rollback to N is safe: old column still written)
-- Deploy N+2: CONTRACT - only after N+1 is stable everywhere
ALTER TABLE charging_session DROP COLUMN legacy_stop_flag;
Three small deploys instead of one risky one — at no point does a rollback strand the schema ahead of the code.
FAQ
As often as changes are ready — for my teams that is several times a week per service. Frequency is a health indicator: when deploys are scary, teams batch; when they batch, risk compounds.
Failed tests, failed contract checks, high-severity CVEs, and missing approvals on gated environments. Everything else warns but never blocks — a pipeline that cries wolf gets bypassed.
Short-lived branches merged to main within a day or two, feature flags for incomplete work. Long-lived branches are merge debt with interest.