Containerize, Deploy and Add HTTPS

4.From the Garage to the High Street

A

In this chapter

On day two, Anna packages her app as a container, deploys it to a cloud platform with a managed database, points a real domain at it and gets HTTPS — reviewing Acts 12, 16, 19, 20 and 21 in her own project, explained as moving a shop from a garage to a high street.

14–16 min

The Problem in Real Life

Friday, 9 AM. Day two. The app works on Anna's laptop — which means it works for exactly one person. "By lunch," she tells John, "anyone with a phone should be able to join a waitlist."

"Then do it the way we do it at BlueTicket," he says. "Container, pipeline, managed database, real domain, HTTPS. Just smaller. And after Sale Day, you know what to check about certificates."

J

Ship it, then improve it.

John

Running on a Laptop vs. Live on the Internet

"Works on my laptop"

The app depends on Anna's Node version, her database and her .env file.

A real address

People need a name to type, not a laptop's IP address.

Trust

Browsers warn users away from sites without valid HTTPS.

Containerising, Deploying and Adding HTTPS

The shop-moving analogy: the shop has been running from a garage. Moving to the high street means packing everything into boxes that fit any van (a container), renting a shop space with electricity and plumbing included (a cloud platform and managed database), putting up a sign with the shop's name (a domain), and getting the official licence displayed in the window (an HTTPS certificate).

  • Containerise (Act 21): a Dockerfile: FROM node:20-slim, npm ci --omit=dev, copy the code, run as the node user, CMD ["node", "src/server.js"]. A docker-compose.yml runs the app and PostgreSQL together locally, so "how to run it" is one command. The same image will run in production.
  • Choose where to deploy (Act 20): for a small project, a PaaS-style container platform (Render, Railway, Fly.io, Google Cloud Run and similar) is ideal: give it the image or the repository, and it runs it, restarts it and handles the load balancer. Add a managed PostgreSQL from the same provider — backups included (Act 20's lesson).
  • Configuration and secrets in the platform (Acts 16, 19): DATABASE_URL, SESSION_SECRET and NODE_ENV=production go into the platform's environment settings — never into the image or Git. The app checks them at start-up and refuses to start without them (Act 19).
  • Run migrations on deploy: the database starts empty. The deploy runs npm run migrate before starting the new version, so the schema is always up to date.
  • Health check (Act 19): a /health endpoint that checks the database connection, so the platform only sends traffic to a healthy app — and restarts it if it isn't.
  • A domain (Acts 12, 19): she points waitlist.anna-builds.example at the platform with a CNAME record in her DNS settings, and checks it with dig.
  • HTTPS (Acts 12, 19, 22, 26): the platform issues a free Let's Encrypt certificate automatically and renews it every 90 days. Remembering Sale Day, Anna checks the certificate's expiry date with openssl, and adds a free external check that emails her if the certificate is ever within 14 days of expiring. Plain http:// requests are redirected to https://, and the session cookie is marked Secure.
Table — Garage to high street
StepShop versionWaitlistTaught in
Container imageBoxes that fit any vanDockerfile, node:20-slimAct 21
Platform + managed DBRented shop with utilitiesContainer platform + managed PostgreSQLAct 20
Config and secretsThe shop's own keysPlatform environment settingsActs 16, 19
Health checkOpening-hours check/health with DB pingAct 19
DomainThe sign above the doorCNAME waitlist.anna-builds.exampleActs 12, 19
HTTPSThe licence in the windowAuto-renewed + expiry alertActs 19, 22, 26

The Event Waitlist, deployed

https://waitlist.anna-builds.example

DNS CNAME → platform

TLS

Platform load balancer + HTTPS

Let's Encrypt, auto-renewed, expiry monitored

Container: event-waitlist:1.0.0

Node 20 · /health · env from platform

DATABASE_URL

Managed PostgreSQL

daily backups

Dockerfile
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
COPY migrations ./migrations
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]
Checking the domain and the certificate
dig +short waitlist.anna-builds.example
# event-waitlist.platform-host.example.
openssl s_client -connect waitlist.anna-builds.example:443 \
-servername waitlist.anna-builds.example </dev/null 2>/dev/null | openssl x509 -noout -enddate
# notAfter=Mar 15 09:12:44 2027 GMT <- valid, auto-renews ~30 days before

11:40 AM — live: Anna opens https://waitlist.anna-builds.example on her phone, on mobile data. The padlock appears. She creates a test event and joins the waitlist from three colleagues' phones. "You are #3 in line." Liam joins from his: "#4". She deliberately stops the database for a moment: /health turns red, the platform stops sending traffic, she restarts it, and everything recovers. It's a real deployment — just a small one.

Key Takeaway

Moving from a laptop to the internet is like moving a shop to the high street: package the app as a container image (Dockerfile, docker-compose for local), deploy it to a container platform with a managed database, keep config and secrets in the platform's environment, run migrations on deploy, expose a health check, point a domain at it with a CNAME, and use automatically renewed HTTPS — then verify the certificate and monitor its expiry yourself.

Why This Matters

A project that's actually live, at a real address with HTTPS, is worth far more in a portfolio than one that only runs locally — and deploying your own project is the best way to make the deployment Acts truly yours. Container platforms and managed databases make this achievable for one person in a morning.

The app is live. Which means things can now go wrong while Anna isn't looking. Before the demo, she wants to know — immediately — if anything breaks.

Next