NET-120 · Lesson 12 of 12 · 26 min

Capstone: deploying a web application to the cloud

▶ Video coming soon A video version of this lesson is on the way.

Over eleven lessons, Jordan and Sam have collected every piece of the wholesale site's design. This capstone puts them together. It follows the site from the final architecture, through the first deployment and the launch checklist, to the first busy Monday morning and the first month's review. Along the way it revisits each lesson's ideas in the order a real deployment uses them. The goal is not to memorize Ridgeline's particular choices, but to see how the decisions connect, so that you can make your own for a different application.

The requirements

Rosa's requirements, agreed in the spring, were short. Wholesale customers must be able to sign in, see their own prices, place orders and download invoices from a phone or laptop. The site must cope with the Monday morning rush and the December peak. It may be down for at most four hours at a time, an RTO of four hours, and lose at most fifteen minutes of orders, an RPO of fifteen minutes. Customer data must stay in the country. And it should cost no more than about 150 dollars a month, roughly what the old ordering system cost in Theo's time spent retyping orders from emails and texts.

Sam added technical requirements of his own: nothing administrative exposed to the internet, MFA for every person, no long-term secrets in code, everything defined in code and reviewed before it changes, every resource tagged, and backups in a second region.

The architecture

An architecture diagram. Customers on phones and laptops reach a CDN and a managed customer sign-in service. The CDN forwards dynamic requests to a load balancer in front of a serverless container service running the wholesale application in two availability zones. The application reads its database password from a secrets manager, stores orders in a managed PostgreSQL database with a standby in a second zone reached through a private endpoint, and stores invoices and photos in private object storage served through the CDN. The application publishes order events to a message service, which triggers functions that email Theo and create invoices. At the bottom, the roastery connects through a site-to-site VPN to a small virtual machine that receives roast logger data. A side panel lists budget alerts, tags, logs and monitoring, backups copied to a second region, and an infrastructure as code pipeline.
The Ridgeline wholesale site in the cloud

Reading the diagram from the customer inward shows how each lesson contributed.

How each part of the design was chosen
PartChoiceLesson
RegionA western United States region (Oregon) for low latency to Idaho, Oregon and Washington customers; backups copied to a second US region3 and 4
EdgeCDN for photos and static files; DNS with a TLS certificate for the site name4
Sign-inManaged customer identity service with MFA option for customers8
ApplicationServerless container platform, two zones, minimum 1 copy overnight, maximum 65 and 9
DatabaseManaged PostgreSQL, small size, standby in a second zone, point-in-time recovery for 14 days, private endpoint only6 and 7
FilesPrivate object storage with versioning and the invoice lifecycle rule; signed links for downloads6
EventsMessage service with two functions: large-order email and invoice creation9
NetworkVirtual network 10.20.0.0/16, public subnets for the load balancer only, private subnets for everything else, no NAT gateway7 and 10
Roast loggerSmall burstable VM with a one-year commitment, reached only over the site-to-site VPN5 and 7
IdentityNamed users in groups with MFA, workload identities for the application, functions and pipeline, secrets in a secrets manager8
OperationsOpenTofu in a repository, plans on every pull request, nightly drift check, policy-as-code checks11
CostTags on everything, a 150-dollar budget with forecast alerts, test environment scheduled for working hours10

The service models are mixed, as lesson 2 predicted: SaaS for invoicing in the accounting package and for customer sign-in, PaaS for the container platform and database, functions for the event handlers, and a single IaaS virtual machine where control of the operating system was unavoidable. The deployment model is public cloud with a small hybrid link to the roastery.

Building it

Jordan built the site in stages, each one a pull request with a plan that Sam reviewed. First came the foundations: the network, the IAM groups and roles, the budget and the tag policy. Then the data layer: the database, the buckets and the secrets entry. Then the application: the container registry, the container service, the load balancer and the DNS record. Finally the extras: the CDN, the message service and the functions, and the VPN to the roastery.

Building a test environment first was the single most useful decision. Because the infrastructure was code, the test environment was the same configuration with smaller sizes and a different name, created in fifteen minutes. Jordan found three problems there that would have been embarrassing in production: the database rule from lesson 7's worked example, a container that ran out of memory when generating large invoices, and a missing permission for the invoice function to write to the bucket. Each was fixed in code, so production never had them.

The application itself is packaged as a container image by an automated pipeline whenever Jordan merges code. The pipeline runs the tests, builds the image, scans it for known vulnerabilities, pushes it to the registry, and deploys it to the test environment. Deploying to production is a separate, approved step.

Deploying safely

Replacing a running application with a new version is a risky moment, and there are several strategies for doing it without downtime. A rolling deployment replaces copies one or a few at a time, so some copies run the old version while others run the new one. A blue/green deployment runs a complete new copy of the application, green, beside the old one, blue, tests it, then switches all traffic at once, keeping blue ready for an instant switch back. A canary deployment sends a small share of traffic, say one request in twenty, to the new version, watches the error rate and response times, and increases the share only if all is well, rolling back automatically if not.

Serverless container platforms make canary and blue/green deployments easy by keeping each version as a revision and letting you split traffic between revisions. Jordan's pipeline sends one request in ten to a new revision for fifteen minutes, compares its error rate with the current revision, and either promotes it to all traffic or rolls it back.

Try it yourself

Promote or roll back?

Complete canary_decision(old_errors, old_requests, new_errors, new_requests, max_ratio, min_requests). It returns "wait" if the new version has handled fewer than min_requests requests; otherwise "rollback" if the new version's error rate is more than max_ratio times the old version's error rate; otherwise "promote".

If the old version had no errors at all, treat any error in the new version as a reason to roll back.

def canary_decision(old_errors, old_requests, new_errors, new_requests, max_ratio, min_requests):
    return "promote"

print(canary_decision(12, 9000, 2, 1000, 1.5, 500), canary_decision(12, 9000, 9, 1000, 1.5, 500))

The launch checklist

Before switching the old email address to an auto-reply pointing at the new site, Sam worked through a launch checklist. It is a good model for any cloud launch, because each line maps to a lesson and to a real failure that has happened to someone.

Ridgeline's launch checklist
AreaCheck
IdentityRoot account locked away with a security key; every person has MFA; no access keys in code or repositories
NetworkDatabase and VMs have no public addresses; only the load balancer is public; no SSH or Remote Desktop open to the internet
DataBuckets block public access; versioning on; database encrypted with automated backups and point-in-time recovery; backups copied to a second region
ResilienceApplication in two zones; database standby in a second zone; health checks configured; a restore tested from backup
OperationsLogs collected; alerts for errors, slow responses and failed health checks go to Jordan and Sam; status page bookmarked
CostAll resources tagged; budget and forecast alerts set; test environment schedule working
CodeEverything in OpenTofu; production console access read-only; nightly drift check running

One item deserves emphasis: a restore tested from backup. Backups that have never been restored are a hope, not a plan. Sam restored the test database from a point-in-time backup to a new instance, checked the orders were there, timed it, about twenty minutes, and deleted the copy. That test proved the RPO and RTO targets could be met.

Try it yourself

Launch readiness

Each resource in the launch inventory is a dictionary with a name, a kind, and some of the keys public (bool), mfa (bool), encrypted (bool) and tags (dict). Complete blockers(resources) to return a sorted list of names that would block launch:

a database or vm that is public; a user without mfa; a database or bucket that is not encrypted; or any resource whose tags do not include project. Missing keys count as False (or as no tags).

resources = [
    {"name": "orders-db", "kind": "database", "public": False, "encrypted": True, "tags": {"project": "wholesale"}},
    {"name": "invoices", "kind": "bucket", "encrypted": True, "tags": {"project": "wholesale"}},
    {"name": "jordan", "kind": "user", "mfa": True, "tags": {"project": "wholesale"}},
    {"name": "old-test-vm", "kind": "vm", "public": True, "tags": {}},
]

def blockers(resources):
    return []

print(blockers(resources))

The first Monday

The site went live on a Thursday, so that any problems would surface before the first real rush. On Monday morning, orders started at six o'clock and peaked just after eight at about two orders a minute. The container service scaled from one copy to four, then back down to two by ten o'clock and one by the afternoon. The database's processor use never passed a third. One customer could not sign in because their password reset email went to a spam folder, which was solved by asking them to check it and, later, by improving the email's sender settings. Theo spent the morning roasting instead of typing orders, which was the whole point.

There was one real incident a few weeks later. A new version that changed how prices were rounded produced errors for orders with more than twenty lines. The canary step sent one request in ten to the new revision; within four minutes its error rate was several times the old one, and the pipeline rolled it back automatically. Two cafes saw an error and retried successfully. Jordan fixed the bug with a test that reproduced it, and the next deployment passed. Without the canary, every customer would have hit the bug.

The first month's review

At the end of the first month, Sam, Jordan, Anita and Rosa reviewed the site against its requirements. Availability was complete apart from a nine-minute window during a provider maintenance event on the database, during which the standby took over as designed. No data was lost. The bill was 131 dollars, under the budget, after Sam removed a forgotten test load balancer worth 18 dollars that the untagged-resources report caught. Customers liked the site, and two new cafes signed up because ordering was easier.

The review also produced a list of improvements, which is normal: add a status page for customers, try a cheaper database size during the quiet summer months, move older roast logs to the archive tier with a lifecycle rule, and write a runbook, a step-by-step guide, for restoring the database, so that someone other than Sam could do it. Cloud systems are never finished; they are measured, reviewed and improved, which is exactly what the operate phase of FinOps and good service management, covered in the ITSM courses, are about.

Summing up the final design's availability and cost
# The capstone in numbers: availability and cost for the final design
def series(*p):
    out = 1.0
    for x in p:
        out *= x
    return out

def parallel(*p):
    fail = 1.0
    for x in p:
        fail *= 1 - x
    return 1 - fail

availability = series(0.9999,                     # CDN and DNS
                      0.9999,                     # load balancer
                      parallel(0.995, 0.995),     # application copies in two zones
                      0.9995)                     # multi-AZ database
minutes = (1 - availability) * 30 * 24 * 60

costs = {"containers": 33.0, "database and standby": 49.6, "storage": 1.2,
         "CDN and egress": 6.1, "functions": 0.0, "roast logger VM": 5.4,
         "VPN": 27.0, "secrets and DNS": 2.4, "logs and monitoring": 6.3}

print(f"Estimated availability {availability:.5f}, about {minutes:.0f} minutes down in a 30-day month")
print(f"Estimated monthly cost {sum(costs.values()):.2f} dollars (budget 150)")
print("Largest items:", ", ".join(k for k, v in sorted(costs.items(), key=lambda kv: -kv[1])[:3]))
Output
Estimated availability 0.99928, about 31 minutes down in a 30-day month
Estimated monthly cost 131.00 dollars (budget 150)
Largest items: database and standby, containers, VPN

Try it yourself

Within budget and targets?

Complete review(costs, budget, availability, max_minutes_down). costs maps items to monthly dollars. Return a dictionary with:

"total": the total cost rounded to 2 places; "within_budget": whether the total is at most the budget; "minutes_down": expected downtime in a 30-day month, (1 - availability) * 43200, rounded to 1 place; "meets_target": whether that is at most max_minutes_down; and "top": the name of the most expensive item.

def review(costs, budget, availability, max_minutes_down):
    return {}

print(review({"containers": 33.0, "database": 49.6, "vpn": 27.0}, 150, 0.9993, 60))

Worked problem

Adapting the design for another client

Sam's next client is a dental clinic that wants online appointment booking, with about forty bookings a day, patient names and phone numbers stored, an RTO of one day and an RPO of one hour, and a budget of about 60 dollars a month. Which parts of Ridgeline's design should change?

Show solution
  1. Personal health-related information raises the privacy stakes: keep data in the country, encrypt everything, restrict access tightly and check the professional body's and province's or state's rules on health information.
  2. Forty bookings a day is a small, spiky load: a scale-to-zero container service or functions fit well, and a single copy is acceptable overnight.
  3. An RTO of one day and RPO of one hour allow a single-zone database with automated backups and point-in-time recovery, saving the cost of a standby; backups still go to a second region.
  4. No on-site equipment means no VPN, which removes one of Ridgeline's larger costs.
  5. Booking and reminders might be better served by an existing SaaS booking product, which should be compared before building anything.

Answer: Keep the security and IaC practices, drop the VPN and the database standby, use scale-to-zero compute, and first check whether a SaaS booking product already does the job.

Common mistakes

  • MistakeLaunching straight into production without a test environment.

    InsteadBuild test first from the same code; most problems surface there.

  • MistakeDeploying new versions to all users at once.

    InsteadUse rolling, blue/green or canary deployments with health checks and automatic rollback.

  • MistakeNever testing a restore.

    InsteadRestore from backup before launch and time it against the RTO.

  • MistakeTreating launch as the end of the project.

    InsteadReview availability, cost and incidents regularly and keep improving.

  • MistakeCopying another organization's architecture without checking requirements.

    InsteadStart from your own RTO, RPO, data rules, load and budget, as the dental clinic example shows.

Summary and key terms

  • Start from requirements: users, load, RTO, RPO, data residency and budget.
  • Combine service models: SaaS where it fits, PaaS and serverless for the application and data, IaaS only where control is needed.
  • Design for failure across zones, keep data private, and use identity, secrets and network controls from the start.
  • Define everything in code, build a test environment first, and deploy through a reviewed pipeline.
  • Deploy new versions with rolling, blue/green or canary strategies and automatic rollback.
  • Use a launch checklist covering identity, network, data, resilience, operations, cost and code, and test a restore.
  • After launch, review availability, cost and incidents, and improve continuously.
rolling deployment
Replacing application copies a few at a time with the new version.
blue/green deployment
Running the new version beside the old and switching traffic all at once, with instant rollback.
canary deployment
Sending a small share of traffic to a new version and increasing it only if it is healthy.
revision
A deployed version of a service on a container platform.
health check
A regular test that a component is working, used by load balancers and deployments.
runbook
A step-by-step guide for carrying out an operational task.
launch checklist
A list of checks to complete before a system goes live.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Why did the site go live on a Thursday rather than a Monday?

    Show answer

    So problems would surface during quieter days before the first Monday rush.

  2. A canary receives 1 in 10 requests. Of 2,000 requests to it, 30 fail; the old version fails 2 in 1,000. Promote or roll back with a maximum ratio of 2?

    Show answer

    New rate 0.015 is 7.5 times 0.002, above the limit: roll back.

  3. Which deployment strategy keeps the old version running for an instant switch back?

    Show answer

    Blue/green.

  4. Name three items from the launch checklist.

    Show answer

    Examples: MFA for every person; database not public; buckets block public access; backups copied to a second region; restore tested; budget alerts set.

  5. Why is a tested restore more valuable than a backup that has never been restored?

    Show answer

    Only a test proves the backup is usable and shows how long recovery takes compared with the RTO.

  6. Give one change from Ridgeline's design that suits a client with an RTO of one day.

    Show answer

    For example, a single-zone database with backups instead of a standby in a second zone.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz