Shipping safely
Deployment is a skill, not a ceremony. The goal is to make releasing boring — small, reversible, and observed.
- You can describe a pipeline that catches problems before users do, and a rollback you'd trust at 2am.
- You know the four security defaults that belong in every first deploy.
A deploy used to mean a ceremony: freeze the code, gather the team, copy files, hold your breath. Modern practice makes it a non-event: automated, small, reversible and observed. That change is not about tooling fashion — it's what allows you to ship on a Friday afternoon without gambling the weekend.
9.1Walk the pipeline
Every push runs the pipeline — no manual start button. The point is that "does it build" is answered by a machine within minutes, not by a colleague on Slack.
Produce one immutable artifact — container image, binary, bundle — tagged with the commit hash. Promote that exact artifact to every environment. Rebuilding per environment is how "it worked in staging" happens.
Fast checks first: formatter, linter, unit tests. Fail in ninety seconds, not after a twelve-minute integration suite. Warnings that matter should fail the build; warnings that don't should be turned off, not ignored.
Dependency vulnerabilities, committed secrets, license rules. This is cheaper than reading every dependency by hand and catches the class of problem you cannot see in a diff.
A production-like environment, same configuration shape, sanitised data. Run the acceptance checks here — the ones that prove the feature works, not just that the code compiles.
Small blast radius: one instance first, or 5% of traffic, or behind a feature flag. Verify with a smoke test — the three or four journeys that must work. If they fail, roll back immediately; diagnose afterwards.
9.2Four ways to make a release boring
- Feature flags. Deploy the code without exposing the feature. It turns "release" into "toggle", and it makes rollback a config change rather than a redeploy.
- Canary or blue-green. Send a slice of traffic to the new version, or switch between two identical environments. Both give you an instant way back.
- Expand/contract migrations. Add the new column, deploy code that writes both, backfill, switch reads, then remove the old. Never make a breaking schema change in a single step.
- A smoke test. Four journeys — sign in, the core action, the payment path, the health endpoint. If they pass, the release is probably fine; if one fails, roll back first.
9.3Security defaults for a first deploy
| Default | Because |
|---|---|
| Secrets in a secret store or env vars — never in the repo, never in logs | A leaked key in git history is a leaked key, even after you delete the file |
| Authorisation checked server-side, on every request | Client-side checks are suggestions; anyone can call your API directly |
| Input validated at the boundary | Most injection bugs start with trusting a string from outside |
| Dependencies pinned and scanned | Lockfiles give reproducibility; scanning tells you when a dependency becomes a liability |
SSHing into a server and pulling the latest code works exactly until the day it doesn't: you can't tell what's deployed, you can't roll back cleanly, and the fix lives in one person's memory. If a deploy needs a human to remember steps, those steps are a bug in the process.
Which practice most reduces the chance of "it worked in staging but broke in production"?
60-second recap
- Build once, promote everywhere. Configuration is injected, never baked in.
- Fail fast: lint and unit tests ahead of the slow suites.
- Small blast radius: flags, canaries, one instance first — and know your rollback.
- Migrations in steps, secrets out of the repo, auth on the server, inputs validated.