158: Deployment, CI/CD, Migrations, Rollback, and Full-Stack Capstone
Learning outcomes
By the end of this lesson, you should be able to:
- explain and apply build and configuration in a realistic implementation;
- explain and apply CI quality gates in a realistic implementation;
- explain and apply database migrations in a realistic implementation;
- explain and apply deployment strategies in a realistic implementation;
- explain and apply rollback and roll-forward in a realistic implementation.
Prerequisites and retrieval
This lesson assumes that you have the 01–06 foundation and have completed the preceding lessons in this module. Before you read on, retrieve one concrete example from an earlier project in which one of these concerns appeared. Perhaps a build depended on an unpinned runtime, a schema change had to be coordinated with application code, or a deployment needed a way to recover safely.
The point is not to memorize a list of release terms. The point is to make a defensible decision inside a production full-stack web application: a React client, a Node.js API, persistent storage, authentication, observability, and the operational concerns that connect them.
Terminology
- Build and configuration: Produce deterministic artifacts, pin supported runtimes, validate required environment configuration at startup, and never bake secrets into client bundles or source-controlled images.
- CI quality gates: Run formatting and linting, type checks, unit and integration tests, dependency and security checks, and production builds before merge or release. The gates should catch defects before they become release problems.
- Database migrations: Use forward-compatible migrations: add before remove, backfill in bounded batches, deploy code that tolerates both schema versions, and remove old fields in a later release.
- Deployment strategies: Rolling, blue-green, and canary releases trade infrastructure cost against risk and the speed at which you receive feedback.
- Rollback and roll-forward: A code rollback may be unsafe after a destructive schema change. Recovery therefore has to account for the state of the database, not just the version of the binaries.
- Capstone evidence: A final project should demonstrate contracts, authentication, authorization, transactions, TanStack Query v5, tests, observability, a security review, deployment, and a written architecture decision record, not just screenshots.
Mental model
Treat Deployment, CI/CD, Migrations, Rollback, and Full-Stack Capstone as a design problem with observable inputs, outputs, invariants, and failure modes. A deployment is a distributed state transition across code, configuration, schema, caches, and traffic. That is why a release cannot be judged only by whether the new process starts: the old and new parts of the system may coexist, and each part must remain compatible while the transition is in progress.
Safe releases are designed around compatibility and recovery. A strong implementation makes assumptions visible, narrows uncertainty at boundaries, and leaves enough evidence—tests, types, constraints, metrics, or diagrams—to explain why the design is safe.
A useful sequence for both production work and interviews is:
requirement -> constraints -> model -> implementation -> failure analysis -> verification
Do not jump directly from a requirement to a library call or deployment command. First state what must remain true. Then choose the mechanism that enforces that invariant and decide how you will observe a failure if the mechanism is wrong.
Deep dive
1. Build and configuration
The same source should produce an artifact that can be understood and reproduced later. That requires deterministic artifacts, supported runtimes that are pinned rather than implicit, and startup validation for required environment configuration. Secrets must not be baked into client bundles or images committed to source control; a value included in a client artifact is available to anyone who can download that artifact.
Decision rule: Use build and configuration deliberately when they make a contract or invariant easier to prove. If a tool only reduces typing while hiding an assumption, prefer the more explicit design.
2. CI quality gates
CI should catch cheap, predictable failures before merge or release. Run formatting and linting, type checks, unit and integration tests, dependency and security checks, and a production build. Keep the gates fast enough that developers will use them consistently; a gate that is so slow or noisy that people bypass it is not a reliable control.
Decision rule: Use CI quality gates deliberately when they make a contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design.
3. Database migrations
Treat a schema change as a compatibility problem, not as a single edit made at the same instant as a code deploy. A safer sequence is to add the new structure first, backfill existing rows in bounded batches, deploy code that can tolerate both schema versions, and remove the old fields in a later release. Bounded work limits lock time and resource pressure, while the compatibility window gives old and new application instances a chance to coexist.
Decision rule: Use database migrations deliberately when they make a contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design.
4. Deployment strategies
Rolling, blue-green, and canary releases make different trade-offs. A rolling release replaces instances gradually, a blue-green release keeps two environments so traffic can switch between them, and a canary release sends a controlled portion of traffic to the new version first. These approaches trade infrastructure cost against risk and feedback speed.
Health checks must represent readiness, not merely process existence. A process that has opened a port but cannot reach a required dependency is running, but it may not be ready to receive traffic. Define the check according to what the application must be able to do safely, and make the decision observable.
Decision rule: Use deployment strategies deliberately when they make a contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design.
5. Rollback and roll-forward
Rolling code back is not always safe. After a destructive schema change, the previous binary may no longer understand the database, even if the binary itself can be redeployed. Prefer reversible or additive database evolution, and document in advance whether a given incident should roll binaries back or roll forward with a fix. The runbook should make that choice based on compatibility and observed behavior rather than on habit.
Decision rule: Use rollback and roll-forward deliberately when they make a contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design.
6. Capstone evidence
The capstone is evidence that the system works beyond its happy-path screenshots. It should demonstrate contracts, authentication, authorization, transactions, TanStack Query v5, tests, observability, a security review, deployment, and a written architecture decision record. The record should capture the decision, the constraints, the alternatives considered, and the reason for the selected design.
Decision rule: Use capstone evidence deliberately when it makes a contract or invariant easier to prove. If it only reduces typing while hiding an assumption, prefer the more explicit design.
Worked example
Consider a production full-stack web application with a React client, a Node.js API, persistent storage, authentication, observability, and deployment concerns. Start with the requirement in one sentence. Then list the input and output contracts and identify which concept owns each failure mode.
The useful boundary is separation of responsibility. Parsing or validation belongs at the boundary; domain rules belong in the domain or service layer; persistence rules belong in the database or repository; presentation rules belong in the client. Combining these concerns can make a happy-path demo look shorter, but it makes edge cases and recovery much harder to reason about.
export async function handleRequest(input: unknown) {
const command = parseCommand(input);
const result = await service.execute(command);
return toHttpResponse(result);
}
This small handler shows the intended flow without pretending that one function owns every rule. Untrusted input is parsed before the service receives it, the service owns the application operation, and the result is translated into the HTTP response at the outer boundary. In a real implementation, each function still needs an explicit contract and error behavior.
Walk through at least four cases:
- The normal path.
- An empty or missing value.
- A duplicate, retry, or concurrent path where that behavior is relevant.
- A dependency failure.
For each case, state which layer detects the problem and what the caller observes. This is the level of explanation expected in a senior code review or technical interview. It also gives you a practical debugging map: if malformed input reaches the service, inspect the boundary; if two valid requests violate a uniqueness rule, inspect the service, repository, and database invariant rather than adding a client-side check.
Production perspective
Production correctness is broader than “the code works on my machine.” Ask how the design behaves during deploys, retries, partial failure, stale clients, concurrent requests, malformed data, schema changes, and high-cardinality telemetry. Prefer explicit contracts, bounded resource usage, structured errors, and measurable behavior. Optimize only after evidence identifies the bottleneck or risk.
When an external dependency is involved, define a timeout and cancellation strategy. When persistence is involved, define transaction and consistency expectations. When state is visible to a user, define loading, empty, error, stale, and success states. When security is involved, assume that the client can be modified and that network input is untrusted. Client behavior can improve usability, but it cannot stand in for server-side authorization, validation, or persistence guarantees.
Guided lab
Build and document the production release of the course task manager. Include CI, container and runtime configuration, one additive migration with a backfill, readiness and liveness behavior, smoke tests, release notes, and a rollback or roll-forward runbook.
Complete the lab with this discipline:
- Write the requirement and two non-requirements.
- List input, output, and error contracts before implementation.
- Implement the smallest correct vertical slice.
- Add at least one invalid-input test and one edge-case test.
- Instrument or inspect the behavior instead of guessing.
- Refactor one hidden assumption into an explicit type, constraint, function, or configuration value.
- Explain one alternative design and why you did not choose it.
- Record a short “what would break at 10× scale?” note.
The lab is complete only when another developer can understand what was released, how readiness was judged, how the migration remains compatible, and what to do when the release does not behave as expected.
Edge cases and failure modes
For each area, test more than the ordinary successful request. Check absence, malformed input, duplicates, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
- Build and configuration: Check missing or malformed configuration, unsupported runtime versions, secret exposure, and whether the artifact is reproducible.
- CI quality gates: Check missing or skipped gates, malformed test data, duplicate work, ordering or concurrency issues where applicable, and behavior at the smallest and largest credible sizes.
- Database migrations: Check missing fields, malformed values, duplicate rows, backfill ordering and concurrency, partial completion, and behavior at the smallest and largest credible data sizes.
- Deployment strategies: Check unhealthy instances, stale clients, duplicate traffic, ordering or concurrency where applicable, and behavior at the smallest and largest credible traffic sizes.
- Rollback and roll-forward: Check incompatible binaries and schemas, partial rollback, repeated retries, ordering or concurrency where applicable, and behavior at the smallest and largest credible sizes.
Common mistakes and debugging
- Solving the example instead of the requirement: a copied pattern can be syntactically correct but architecturally wrong.
- Hiding uncertainty with assertions, broad exception handlers, permissive schemas, or “temporary”
anyvalues. - Testing only the happy path, then discovering the real contracts during integration.
- Optimizing before measuring, or selecting a scalable mechanism without a scale requirement.
- Letting client-side behavior stand in for server-side authorization, validation, or persistence guarantees.
When debugging, reproduce the smallest failing case first. Inspect the actual value or execution plan, trace the boundary where the invariant first becomes false, and fix the layer that owns the invariant instead of adding a downstream patch. The relevant boundary may be the source or build, browser or DOM, Network panel or HTTP exchange, server or route, database or query, or deployment or configuration.
Interview questions
- What problem does build and configuration solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do CI quality gates solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do database migrations solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do deployment strategies solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do rollback and roll-forward solve, and what trade-off or failure mode would make you choose a different approach?
Checkpoint
Without notes, explain Deployment, CI/CD, Migrations, Rollback, and Full-Stack Capstone to another developer in five minutes. Your explanation must include one invariant, one edge case, one production failure mode, and one alternative design. Then implement a small example without copying the lesson code.
Mastery checklist
- I can define the core terms precisely.
- I can choose a design from requirements instead of from habit.
- I can implement and test the normal path and edge cases.
- I can explain the runtime, storage, or complexity cost.
- I can identify which layer owns validation, errors, and recovery.
- I can compare at least two reasonable alternatives.
- I can explain how the design changes at larger scale or stricter reliability.
