Home

/

The Production-Ready Playbook

/

Hosting: Cloud-Agnostic by Default

Hosting: Cloud-Agnostic by Default

Chapter 9
Part II
4
min read

You have an order service that is testable, resilient, and observable. Now it has to run somewhere. The trap at this altitude is the one the cloud vendors quietly want you to fall into: build for their managed runtime, wire in their proprietary glue, and discover two years later that "moving clouds" means a rewrite. A small team cannot afford that bet. The defence is older and duller than any vendor's roadmap, and it fits on a single page.

The stance for the whole chapter is cloud-agnostic, container-first. The container is the portability boundary. Everything specific to one cloud lives outside it, in configuration and infrastructure code you own. Get that boundary right and the same order-service image runs on Cloud Run (AWS App Runner/ECS, Azure Container Apps) without you noticing which one you picked this morning.

The Container as the Unit

Your build artifact is one thing, and it is an OCI image. Not a zip of DLLs that the ops person unpacks onto a VM, not a deployment script that assumes a particular host. The unit you ship, test, and run in every environment is the same image, built once.

The discipline that makes this work is the Twelve-Factor App (Wiggins/Heroku, 2011). Treat config as environment, the build as a one-time step, the run as a separate one, and the process as disposable. The Dockerfile is where most of that lands.

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app .
EXPOSE 8080
ENTRYPOINT ["dotnet", "OrderService.dll"]

What it buys you in production: one artifact, identical from your laptop to staging to live. The "works on my machine" failure mode disappears because the machine travels with the code. The image is also the contract every later pattern in this chapter leans on. Scale-to-zero, orchestrator-agnostic deploy, blue-green rollout, all of them assume the unit is an image you can start, stop, and replace at will.

The container is the only thing you ship. If the cloud knows anything about you that the image doesn't, you have a lock-in bug.

Skip-if: you genuinely have one long-lived server and no second environment, and you never will. Then a published binary and a service manager is less to carry. The moment a second environment or a second instance appears, the image earns its keep.

Stateless + Externalised State

Treat instances as cattle, not pets (the phrase is folklore, the discipline is twelve-factor's). Any one of them can vanish mid-request and be replaced by an identical one, and nothing important is lost. That only holds if the instance keeps no state worth keeping. A half-built cart, an Order still being placed, an in-memory menu cache the next request depends on: all of it has to live somewhere the instance doesn't.

So you push state out. The customer's cart and order state go to a shared store (Memorystore for Redis; ElastiCache, Azure Cache for Redis) or straight to the OrderGateway. Uploaded restaurant photos go to object storage (Cloud Storage; S3, Azure Blob). Anything you were tempted to hold in a static field gets a real home outside the process.

// Not this — lost the instant the instance is replaced
private static readonly Dictionary<string, Cart> Carts = new();

// This — state lives outside the process, any instance can serve any request
public async Task<Cart?> GetCart(string id)
{
    var bytes = await _cache.GetAsync($"cart:{id}");
    return bytes is null ? null : Deserialize<Cart>(bytes);
}

What it buys you in production: when the dinner rush hits you scale out by adding identical instances and scale in by killing them, with no sticky sessions and no "which box has my cart" debugging at 3am. It is the precondition for every elastic behaviour the cloud offers. Stateful instances quietly cap you at one.

Skip-if: nothing. This is the price of admission for the rest of the altitude. If an instance holds state that can't be rebuilt or refetched, fix that before you reach for autoscaling, because autoscaling will simply expose it as data loss.

the-pareto-stack-cloud-design-patterns-for-small-teams
the-ladder-of-altitudes
how-to-read-this
object-level-the-patterns-that-earn-their-keep
decorator
state
component-level-structuring-one-service
ports-and-adapters-hexagonal
mediator-the-commandquery-split
data-persistence
optimistic-concurrency
messaging-scale
outbox
resilience-staying-up-when-dependencies-dont
rate-limiting-throttling
timeout-fallback
the-composed-pipeline
observability-diagnostics-seeing-inside-production
metrics-the-four-golden-signals
externalised-configuration
hosting-cloud-agnostic-by-default
sidecar-ambassador
orchestrator-agnostic-deploy
a-reference-service
the-relay-outbox-to-queue
the-payment-saga-charge-pay-out-compensate
the-over-engineering-tax
conclusion-production-ready-deliberately
the-pattern-quick-reference-card
altitude-3-data-persistence
altitude-5-resilience
the-skip-list
full-event-sourcing-for-crud
robert-c-martin-uncle-bob-the-house-authority-for-structure
altitude-2-component
altitude-4-messaging-scale
altitude-6-observability-diagnostics

Download the full PDF for free?

Free download — no account required

Get the PDF
Get the PDF
Related Chapters
Free Download
Get the full PDF
All pages, including all code examples, diagrams, and the appendix reference card.
No spam. Unsubscribe at any time.
Your email won't be shared.
Oops! There's a problem with your request. We're working on fixing it. Please try again later.