Azure and DevOps: from a commit to production
How a change travels from a commit to production safely: CI and CD, Azure DevOps pipelines, supply-chain scans and SBOMs, Docker images, Azure building blocks, Cloudflare tunnels, releases with migrations first and automatic rollback, backups and OpenTelemetry - with pictures, generic code and interview answers.
In short
The craft that gets code safely into production is DevOps. CI builds and tests every change. CD keeps every good build ready to release. The servers, databases and networks are run so that a bad release can be undone. {{brand}} uses Azure DevOps pipelines, Docker images, a managed PostgreSQL database and a Cloudflare tunnel.
After this lesson you can 1. Read a YAML pipeline. 2. Explain how scans and an SBOM protect the supply chain. 3. Build a small, safe Docker image. 4. Describe a release with migrations first and automatic rollback.
Words you need
- CI Continuous integration: every change is merged often and built and tested automatically, so a break is found within minutes.
- CD Continuous delivery: every green build is ready to release at the press of a button. Continuous deployment goes one step further and releases with no button at all.
- Pipeline and agent A pipeline is a list of stages, jobs and steps written in YAML. An agent is the machine that runs a job.
- Artifact A file a pipeline produces and keeps: a package, a test report, an image. The next stage uses exactly that file.
- Environment A named place a release goes to, such as staging or production. It can demand an approval before anything runs there.
- Image and container An image is an application packed with everything it needs. A container is that image running, isolated from its neighbours.
- Vulnerability scan A tool compares what you ship with a list of known security holes (CVEs) and reports the matches, each with a severity.
- SBOM Software bill of materials: the complete list of the packages inside an image, like the ingredients on a food label.
- Managed identity An identity Azure gives a machine, so it can reach other services without any password stored anywhere.
- Health check A small question a platform asks the app: are you alive, and are you ready for traffic? An orchestrator or a release script decides restarts and rollbacks from the answer.
- Rollback Going back to the previous version when a release turns out unhealthy.
- Tunnel An outgoing connection from your server to an edge service, so visitors arrive without an open inbound web port.
CI and CD: what each one promises
Teams mix up three terms. They are three levels of trust in automation: integration, delivery and deployment, and each one adds a promise. Knowing which one you have tells you how risky a Friday release is.
- 1. Commit and pull request A developer pushes a small change. A branch policy blocks the merge until the build is green and a reviewer agrees.
- 2. Build The code is compiled from a clean checkout on a clean machine. If it works only on one laptop, it fails here.
- 3. Test Unit tests, integration tests, and tests against a real database, so the change is checked before anyone depends on it.
- 4. Check and scan Linting, architecture rules, package audits and image scans. Fast, automatic, no opinions.
- 5. Package The result becomes an artifact with a version number: a container image, a zip, a package.
- 6. Approve For production, a named person looks at what is about to ship and says yes.
- 7. Release and watch Deploy, check health, and keep watching. A release is finished when it is proven healthy, not when the script ends.
CI works only when branches are short-lived. In trunk-based development everyone merges small changes to the main branch at least daily, and branch policies (a required green build, a required reviewer) protect it. A long-lived branch turns every merge into a small project of its own.
Ship what you tested The thing you tested must be the thing you ship. The safest way is to build once and promote that same artifact through every environment. If you rebuild, build from the exact same commit and run the checks again, because a rebuild can pick up new package versions and fresh base-image fixes.
Azure DevOps pipelines, from the inside
Azure DevOps is a hosted suite: repositories, work boards, test plans and Pipelines. A pipeline is a YAML file that lives in the repository next to the code. Because it is code, it is reviewed, versioned and rolled back like code. The service reads the file; it does not run your steps itself. Agents run them.
- Trigger fires A push, a schedule, a policy on a pull request or a person pressing Run.
- The YAML is compiled The service reads the file, applies parameters and the ${{ }} template expressions, and produces the real plan of stages and jobs.
- Jobs wait for an agent Each job is queued for the pool it names. An idle agent takes it. With no agent free, the job waits.
- The agent runs the steps It checks out the code and runs the steps one by one as ordinary processes. Each step starts in a clean shell.
- Output flows back The log streams to the web page. Special lines such as ##vso[...] set variables, mark errors or add tags to the run.
- Artifacts are published Files worth keeping are uploaded, so a later job or stage can download exactly them.
- Dependents start A job that dependsOn this one starts if it succeeded. A failed job stops the chain and turns the run red.
Three kinds of expression look alike and run at different moments. A template expression is filled in when the YAML is compiled, a macro just before a step runs, and a runtime expression while the run executes. Mixing them up is the commonest YAML surprise.
Agents dial out A self-hosted agent opens an outgoing HTTPS connection to Azure DevOps and asks for work, so the target needs no open inbound port for deployments. This is safer than letting a cloud service log in to it.
Secrets in pipelines Secret variables are masked in logs, but masking only matches the exact text. Never print them, never put them in a script argument that is echoed, and never pass them as Docker build arguments. Prefer a service connection or a managed identity, which hold no secret you could leak.
Docker images and multi-stage builds
An image is a read-only stack of layers: an operating-system base, then your files, each step of the Dockerfile adding a layer. A container is a process started from an image, with its own file system and network, sharing the host's kernel. That is why it starts in a second and why the same image behaves the same everywhere.
- Layer One step's changes to the file system. Docker caches layers, so a step reruns only when its input changed.
- Tag A human-friendly name for a version, like 1.4.0. It can be moved to point at a different image.
- Digest A hash of the image's exact content, like sha256:9f2c.... It can never point at anything else.
- Registry A server that stores images. Without one, an image can be saved to a file and loaded on the target machine.
A multi-stage build uses several FROM lines in one Dockerfile. The first stage has everything needed to build: compilers, package managers, test tools. The last stage starts again from a small base and copies in only the finished output. The build tools never reach production.
- Build stage Start from the big SDK image. Copy the package list first and restore, so that layer is cached until packages change.
- Compile Copy the source and publish a release build into one output folder.
- Runtime stage Start from a small runtime image with no compilers and no shell tools you do not need.
- Copy across COPY --from=build takes only the output folder. Nothing else survives.
- Drop privileges Create a normal user and switch to it. A hacked app then has no root rights inside the container.
- Describe how to check it HEALTHCHECK tells Docker how to ask the app if it is ready. A platform can then act on the answer.
A tag moves; a digest does not node:22-alpine today and node:22-alpine next month are different images. When an image is pinned by digest, the build is repeatable and a poisoned re-tag cannot slip in. When you pin, put the update in your routine: a weekly report and a reviewed change that moves the pin.
Build arguments are not secret Values passed with --build-arg are recorded in the image history. Values compiled into a web bundle, such as an API address, end up in files every visitor downloads. Treat both as public. For a real secret at build time use BuildKit secret mounts.
Compose describes a group of containers on one machine. Two networks work like firewall rules: only the container that meets visitors sits on both sides.
The options in the market
- Kubernetes (AKS, EKS, GKE) A cluster that schedules containers across many machines.; Rolling updates, self-healing and autoscaling built in; The standard for many services and teams; Huge ecosystem; Many moving parts to learn, secure and upgrade; A cluster is a product you run; Overkill for a few containers
- Managed containers (Container Apps, App Service) The cloud runs your containers and scales them.; No machine to patch; Scale-out and revisions with traffic splitting; Quick to start; Less control over the host; Billing follows reserved resources, which can cost more than a plain machine for steady workloads; Platform-specific features tie you in
- A virtual machine with Docker and Compose One or a few Linux machines running containers you start yourself.; Simple enough to explain on a whiteboard; Full control of network, disks and sidecars; Predictable cost; the same Compose file runs on a laptop; You own the operating system and its updates; Scaling out is a manual step; You build the deploy and rollback logic yourself
- Serverless functions Code that runs only when called; the cloud does the rest.; Pay per call; scales to zero; Nothing to patch; Awkward for long-running or stateful work; Cold starts; Hard to run the same way locally
- Azure DevOps Pipelines YAML pipelines in the same suite as repositories and boards.; Environments with approvals and checks; Self-hosted agents that dial out; Code, work items and builds in one place; Smaller community than GitHub; Free plans have a limited number of hosted minutes
- GitHub Actions Workflows next to code on GitHub.; Huge marketplace of ready-made actions; Great for open source; Tied to GitHub; Some deployment protections depend on the plan
- GitLab CI Pipelines built into GitLab.; One product from code to deploy; Strong built-in security scans; Tied to GitLab; Self-hosting it is a job of its own
- Jenkins The classic self-hosted automation server.; Endless flexibility; You own everything; You run, patch and secure it; Plugins age and conflict
- Application Insights Azure's monitoring service. A library in the app sends requests, dependencies and errors to a workspace.; Minutes to set up in Azure; Application map, live metrics, alerts; A daily cap limits the cost; Tied to Azure; Cost grows with volume
- OpenTelemetry with a collector The open standard. The app speaks OTLP to a collector you choose, which forwards to any backend.; No lock-in; change the backend without touching code; One standard for logs, metrics and traces; The collector can scrub data; You choose and run the backend and the collector; More to learn at the start
- Log files and grep Text logs on the server.; Nothing to set up; Works on day one; No view across requests or services; Searching is slow and manual; Gone when the machine is
{{brand}} supports both through a setting: Application Insights where Azure is the home, or any OpenTelemetry collector where it is not.
What {{brand}} chose, and why
{{brand}} chose the simplest setup that is still safe and repeatable. Azure DevOps holds the code and runs the pipelines. Docker images run on Azure. The database is a managed PostgreSQL service, files live in object storage, and a tunnel fronts the site. Each of these is explained in the sections below; come back here after reading them to see how they fit together. The reasons:
- One team, one product. A setup that one person can hold in their head beats a cluster that needs a specialist. Every part is a container, so the move to something bigger stays open.
- State lives outside the machines. The database is managed (patches and backups are Azure's job) and files live in object storage. A machine can be rebuilt from scratch because nothing irreplaceable lives only on it.
- Small attack surface. Nothing is exposed that does not need to be; the database is reachable only from inside the private network.
- Heavy checks, deliberate release. Every change runs the full build and scans automatically. Production is changed only by a person who picks a green build and approves it.
- Rollback is built in. A release that does not become healthy is replaced by the previous one without anyone typing.
The trade-off: you own more of the operating work, in return for a setup you understand end to end and a bill that is easy to predict.
When another option is right Choose Kubernetes when many services and teams need standard scheduling. Choose managed containers when traffic is spiky and you want scaling without machines. Choose serverless for event-driven glue. And once you have more than one environment, describe your infrastructure as code (Bicep or Terraform, see the section on infrastructure as code) instead of clicking in a portal.
Environments, approvals and the release pipeline
A release is a different job from a build. A build runs on every change and may fail often. A release changes production and should happen only on purpose. So the two are separate pipelines, or at least separate stages, joined by an artifact and a gate.
- The build stage makes artifacts Images and a bundle of deployment files, published with a version number.
- Check what was selected The release refuses a build that did not succeed or did not come from the main branch, then takes that build's exact commit.
- A deployment job names an environment environment: production ties the job to a named target with its own history and rules.
- The checks run first The environment asks its approvers. Until they say yes, none of the steps starts.
- A self-hosted agent runs the steps A job can run in a pool of your own agents, which can reach local Docker and private addresses.
- The history is kept The environment records what was deployed, from which run, approved by whom.
The YAML also has strategy: settings for a deployment job: runOnce does it once, rolling updates a few targets at a time, and canary sends a small share first. Which one fits depends on how many copies of the app you run.
Approval is a control, not a ceremony An approval only means something if the approver sees what will change: the version, the commit, the scan results. Put those in the run's title and tags. Remember, too, that the person who pushed the code should not be the only person who can approve it.
Supply-chain security: scans, SBOMs and trusting your tools
Most of the code in a product is not yours. It comes from packages, base images and build tools. A supply-chain attack goes after those: a poisoned package, a name that looks like a popular one, a stolen publisher account, a tampered base image, a compromised build tool. You cannot read every line you depend on, so you automate the checking and keep a record.
- Audit at restore Treat warnings as errors, so an advisory for any package, even a transitive one, stops the build.
- Audit what ships Run dotnet list package --vulnerable and npm audit and fail on high or critical findings. Leave out development-only tools.
- Build the image Upgrade the system packages inside the build.
- Scan the image Trivy lists the operating-system and library packages inside and matches them with advisories.
- Write the bill of materials Save the package list as CycloneDX and keep it with the run.
- Rescan on a schedule A weekly run with always: true, because yesterday's clean image gets a vulnerability today without a single change.
- CVE and advisory A numbered public record of a security hole, and the note saying which versions are affected and which fix them.
- Severity Low, medium, high or critical, often from the CVSS score. A policy picks the level that fails a build.
- Unfixed A finding with no fixed version yet. --ignore-unfixed leaves these out of the gate, since no action is possible; record them as accepted risks.
- SBOM The inventory of every component, with name, version and a package URL (purl) that names it unambiguously.
- Signature A cryptographic proof that an image was made by someone holding a key. Tools like cosign sign by digest.
Scan again later An SBOM turns a security alert into a question you can answer in seconds: does any of our images contain the broken package? A scanner can read an old SBOM against today's advisories. Without the list, you must rebuild and hope.
Your scanner is software too Security tools run with wide access. Tags of a popular scanner have been replaced with malicious builds in the past. The lesson is general: pin tools by digest or checksum, look the digest up from a trusted source, and move the pin through a reviewed change.
A short policy keeps this honest. A fixable high or critical finding fails the build. An unfixable one is written down with a reason and a date to look again. A red scheduled run is treated as a security finding, not as noise.
Azure building blocks
Azure is a catalogue of services. A small production setup needs only a few. Knowing what each one is for - and which parts are yours to look after - is what interviewers test.
- Create a virtual network Choose an address range that does not overlap other networks you may join later.
- Delegate a subnet Reserve one subnet for the database service. Only that service lives in it.
- Create the server with private access It receives a private address and no public one. There is nothing on the internet to attack or to misconfigure.
- Link a private DNS zone The server's name now resolves to the private address, but only inside your network.
- Put the VM in the same network The app reaches the database over private addresses; nothing crosses the internet.
- Verify the certificate Connect with TLS and the strictest mode, so the app checks it is talking to the right server.
Encrypted is not the same as verified In many database drivers the require TLS mode encrypts the connection but does not check who answered. Use verify-full (or your driver's equivalent) so the certificate and the server name are both checked.
The same idea removes passwords between Azure parts. A managed identity lets a machine prove who it is by asking Azure's metadata service, a link-local address reachable only from that machine, for a short-lived token. You then grant that identity a role on the target, for example *Storage Blob Data Contributor*. This is role-based access control.
- Enable the identity Switch on a system-assigned identity for the virtual machine.
- Grant a role On the storage account, give that identity the least role that works.
- The app asks for a token The SDK calls the metadata address. No secret is needed to ask.
- Azure issues a short-lived token It expires in hours and names exactly this identity.
- The service checks the role Storage accepts the call because the role allows it. Nothing sensitive was ever stored.
Key Vault is for what cannot be avoided: third-party API keys, signing keys, certificates. It keeps secrets, keys and certificates apart, logs every access, and supports soft delete. A common pattern is to load the vault into the application's configuration at start, so the code only reads settings. Secret names allow letters, digits and dashes, so double dashes stand in for the colons of nested settings.
Infrastructure as code
Infrastructure as code means the machines, networks and databases are described in text files kept in the repository, not created by clicking in a portal. A tool reads the files and makes the cloud match them. The result is reviewable, repeatable and recoverable: you can rebuild an environment from the repository instead of from memory.
- Write the template Describe the resources you want in a file, next to the code that needs them.
- Preview the change The tool compares the file with what exists and lists what it would create, change or delete. Azure calls this a what-if, Terraform a plan.
- Review in git A pull request shows the same change to a second person before anything happens.
- Apply The tool makes the cloud match the file. Nobody clicks.
- Keep it true Change things only in the file. A hand edit in the portal is drift, and the next preview shows it.
- Portal clicks Create each resource by hand in the web console.; Fine for a first experiment; Nothing to learn; Nobody can say exactly what was clicked; Impossible to review or repeat reliably
- Bicep Azure's own template language; it compiles to Azure Resource Manager templates.; New Azure features are supported early; No state file to keep: Azure itself is the record; Short and readable; Azure only
- Terraform A tool and language that works across many clouds and services.; One tool for Azure, other clouds and SaaS products; Large module ecosystem; Keeps a state file that must be stored and protected; A new language to learn
State and secrets A Terraform state file can contain secrets in clear text. Store it in a protected remote location, and never commit it to the repository.
- Why describe infrastructure as code? It makes changes reviewable and repeatable, and lets you rebuild an environment from the repository instead of from memory.
From visitor to server: Cloudflare, tunnels and private networks
The classic way to publish a site is to open ports 80 and 443 on a server and point DNS at it. Then the whole internet can reach your machine directly, including scanners and floods. A tunnel turns the connection around.
- DNS points at the edge The domain's records lead to Cloudflare, not to your server.
- TLS ends at the edge The visitor's browser makes an HTTPS connection to Cloudflare's nearest location.
- The edge filters Floods, known bad bots and challenge pages are handled before anything reaches you.
- Into the tunnel The request travels down a connection that a small program, the connector, opened earlier from your server outward.
- The connector hands it on It forwards the request to the application over a private network.
- The app takes over The application serves the page or calls its API, and the API talks to the database on a private address.
- Open ports, nginx and certificates You publish the machine and protect it yourself.; Everything is yours; no third party in the path; Well understood; Every port is a door to watch; Certificate renewals to babysit; No flood protection by default
- Cloud load balancer or application gateway The cloud's own front door.; Integrates with the platform; Web firewall options; Runs and bills all the time; Another resource to configure
- A Cloudflare tunnel An outgoing connector and an edge in front.; No inbound web ports; TLS, DNS and basic protection at the edge; Easy to move to another machine: start the connector there; A third party sees traffic in clear text at the edge; The origin must refuse traffic that skipped the tunnel
- A VPN or private link only No public access at all.; The smallest attack surface; Only for staff and internal tools, not for the public
Lock the origin A tunnel moves trust to the edge. Make the origin refuse anything that did not come through the tunnel, and trust a forwarded visitor address only from a known source. Otherwise the origin can be fooled about who is calling.
The rule that connects this section to the last one: expose as little as possible. Aim for this shape: the app reached through a tunnel, the database on a private address, storage reached by identity, and secrets never in the repository.
A release without drama: migrations first, health checks, rollback
A release is risky for two reasons: the new code might be wrong, and the database has to change at the same time. The calm way is to make both reversible, in small steps, with a check at the end.
- Pick a green build Choose a build whose tests and scans passed. The release refuses a run that did not succeed.
- Build or promote the image, and scan it Same commit, same checks. A fixable high or critical finding stops it here, before anything reaches the server.
- Approval A person reads the version and approves.
- Load the image on the target The agent loads the images and the deployment files onto the target.
- Migrate while the old version serves The new image's migration tool upgrades the database. It runs as a separate step, not at app start, so a failure stops the release and nothing is switched.
- Switch Start the new version.
- Check Wait until the health check says the app is ready, or give up after a few minutes.
- Roll back by itself If it never becomes healthy, start the previous version again. The old image is still on the server for exactly this reason.
Step 5 only works if the old code runs on the new database. That is the rule behind expand and contract: change a schema in a way that two neighbouring versions can both live with.
Databases do not roll back like code Running a migration backwards often loses data, so the safe rollback is old code on the new, additive schema. If a bad release damaged data, the way back is a restore to a time before the release (see backups below). A good habit is to write down the time just before a release. A test that scans migrations and fails on a drop or a rename turns the rule from a promise into a gate.
Use a startup probe for apps that take a minute to warm up, so liveness does not kill them too early.
Why must liveness not look at the database? Imagine the database is down for a minute. If liveness checked it, the platform would restart every copy of the app at once, and the app would then fail to start: a restart storm. Readiness may check the database: not ready means "send no traffic here", which is exactly right.
- Recreate, gated by a health check Stop the old, start the new, wait for ready, go back by itself if it never comes.; The simplest to understand and to script; Never two versions at once; The previous version is always one command away; A short pause while the new one starts; Only as good as the health check
- Rolling Replace the copies a few at a time.; No gap; Gradual; Needs several copies; Two versions run together for a while
- Blue-green Run a full second copy, then move the traffic across.; Instant switch and instant switch back; Needs double the capacity; The database is still shared
- Canary Send a small share of traffic to the new version first.; Real users test it in small numbers; Needs good metrics and traffic routing
A health gate with automatic rollback works with every strategy above; it is the part that makes any of them safe.
- Keep releases small; a small change has a small blast radius.
- Hide unfinished work behind a feature flag, so releasing code and enabling a feature are separate decisions.
- Tag everything with the version and the commit, so you can say what runs where.
- Keep the previous image on the server, and rehearse the rollback before you need it.
Backups and restore drills
- RPO Recovery point objective: how much recent data you can afford to lose, measured in time. A nightly backup means up to a day.
- RTO Recovery time objective: how long you can be down while restoring.
- Logical backup A dump of the data as commands or rows, for example pg_dump. Portable, slow for big databases.
- Point-in-time restore Base backups plus a continuous log of changes, so you can rewind to a chosen minute.
- Retention How long backups are kept. Longer retention catches mistakes noticed late.
- Base backups and a change log The database takes regular full backups and archives every change continuously.
- Choose the moment Pick a time just before the mistake. Release times noted beforehand help here.
- Restore into a new server The service builds a new server from the backups and replays the log to that moment. The original is untouched.
- Check it Compare row counts, open the data, run the application against it.
- Switch over Point the application's connection at the new server, or copy the needed rows back.
Managed PostgreSQL on Azure keeps this change log for a retention period that can be as long as 35 days, and it always restores to a new server. That makes a practice restore safe: nothing existing is overwritten.
- Restore somewhere harmless A scratch database or a throwaway server, never the live one.
- Check integrity Compare the files against the checksum list written at backup time.
- Look inside Count rows in a few important tables; check that the schemas are all there.
- Prove the keys Start the application against the restored data and read something that was encrypted.
- Write down the date and the time it took That time is your real RTO.
A backup you never restored is a hope Restore drills find the broken script, the missing key and the expired password while it is cheap to fix them. Run one before launch, after changes to the setup, and on a calendar.
- 3-2-1: three copies, on two kinds of storage, one of them somewhere else.
- Keep encryption keys apart from the data they protect. A key next to its data protects nothing.
- Make servers replaceable: configuration and keys saved elsewhere, so a lost machine is rebuilt, not repaired.
- Turn on soft delete for object storage; it costs little and undoes mistakes.
Logs, traces and OpenTelemetry
When something is slow or broken at 3 a.m. you need to answer three questions. What happened? How often? Where did the time go? Each has its own kind of data, and together they are called observability.
- Span One timed operation inside a trace, such as an HTTP call or a database query.
- Trace id A 32-digit hex number shared by every span of one request, across services.
- Correlation id The id you put in logs, error pages and support tickets. Using the trace id as the correlation id ties everything together.
- OTLP OpenTelemetry's protocol for sending logs, metrics and traces. Port 4317 for gRPC and 4318 for HTTP are the usual defaults.
- Collector A small service that receives telemetry, can filter or scrub it, and forwards it to a backend.
- Sampling Keeping only a share of traces to control cost. Keep every error, sample the rest.
- A request arrives The web server starts a root span and reads the traceparent header if the caller sent one.
- Context is attached The trace id goes into the logging scope, so every log line of this request carries it.
- Calls become child spans The database query and each outgoing HTTP call become spans, and the traceparent header is passed on.
- The response returns the id The correlation id is added to the response header and to error bodies, so a user can quote it.
- Telemetry is exported The SDK sends spans, metrics and logs over OTLP, in batches, to a collector or straight to a backend.
- You search by id Given an id from a ticket you open the trace, then the logs of the same request.
Do not log what you should not keep Log the route template (/orders/{id}), not the raw address, because tokens in links end up in logs. Log a user id, never an email. Cut query strings from exported spans, and scrub errors reported by browsers, which cannot be trusted to clean them. Log levels follow the status class: 2xx as information, 4xx as warnings, 5xx as errors, and probes quietly at debug.
Application Insights is Azure's way to receive all this. A library in the app collects requests, dependencies and exceptions with almost no code. Adaptive sampling thins busy periods. A daily cap on the workspace stops a surprise bill. {{brand}} lets an operator choose: nothing, Application Insights, or any OpenTelemetry collector.
- Check from outside. An uptime checker that runs somewhere else asks the readiness address every minute. If it runs on the same machine, it dies with it.
- Keep the status page apart from the product, so it stays up when the product does not.
- Alert on symptoms people feel (errors, slowness), not on every cause.
Try it yourself: a small image, a scan and a health-gated rollback
You need Docker, and optionally Trivy. Nothing here costs money and it all runs on a laptop. By the end you will have seen the whole release loop in miniature.
- Write the app A 15-line Node server with a liveness and a readiness address. Readiness says no for the first five seconds.
- Write the Dockerfile Run as a normal user and add a health check against the readiness address.
- Build and scan Build version 1, scan it with Trivy, and write a bill of materials. Open the bill and find the base image's packages.
- Watch it become healthy Run the container and ask Docker for its health: first starting, then healthy.
- Make version 2 broken Edit server.mjs, delete the setTimeout line so ready stays false, and build it as demo:2.
- Release with the script Run the small release script below with version 2. It waits for the health check, gives up when the container never becomes healthy and starts version 1 again.
- Check the result The release script exits with an error, and the container named demo runs demo:1 again. Asking for the readiness address answers ready.
Common mistakes
- Shipping something other than what was tested. A rebuild with different package versions is a new, untested build. Build once, or rebuild from the exact commit and run the checks again.
- Migrations that break the running version. Dropping or renaming in the same release as the code change makes rollback impossible. Use expand and contract.
- Health checks that lie. A liveness check that calls the database causes restart storms; a readiness check that always answers 200 sends traffic to a broken copy.
- Secrets in the wrong place. Keys in the repository, pipeline logs, build arguments or image layers. Use managed identities, a vault and secret variables.
- Floating versions for tools and bases. latest today is something else tomorrow, and a scanner you do not pin can be the thing that is compromised. Pin by digest and move the pin on purpose.
- A backup never restored, or keys kept next to the data. Run drills, and store keys apart from what they protect.
Interview questions
- What is the difference between continuous integration, delivery and deployment? CI builds and tests every change often. Continuous delivery adds a releasable artifact that any green build can turn into a release at a button press. Continuous deployment removes the button: every green build goes to production automatically.
- What are the parts of an Azure DevOps pipeline, and where do agents come in? A pipeline has stages, jobs and steps, written in YAML. Each job runs on an agent from a pool. Microsoft-hosted agents are fresh machines per job; self-hosted agents are your own machines that dial out to the service, which suits long jobs and deployments to private servers.
- How do you secure the software supply chain? Audit packages on every build and fail on high findings. Scan images with a tool like Trivy and keep an SBOM for each. Pin tools and bases by digest, rescan on a schedule, and sign what leaves your hands.
- What is an SBOM and when does it pay off? It is the full list of components inside an artifact, with versions and package URLs. When a new vulnerability is announced you can check old SBOMs in seconds instead of rebuilding and guessing.
- Why use a multi-stage Docker build? The first stage holds the compilers and tools; the last stage copies in only the output. The image is smaller, has fewer packages to be vulnerable, and ships no build tools or source. Layer order also makes rebuilds fast.
- How does a VM reach storage without a password? With a managed identity. The VM asks Azure's local metadata endpoint for a short-lived token, and the storage account allows the call because the identity holds a role such as Blob Data Contributor. No secret is stored, so none can leak.
- How do you deploy a database change without downtime? Use expand and contract: add the new structure first, keep the old one, move the code in a later release, and remove the old structure after that. The old version must work on the new schema, so rollback is the previous image.
- Explain liveness and readiness probes. Liveness asks only whether the process is alive and touches no dependency; a failure means restart. Readiness asks whether this copy should get traffic and checks the database; a failure means take it out of rotation.
- What are RPO and RTO, and how do you know your backup works? RPO is how much data you can lose; RTO is how long you can be down. You only know a backup works by restoring it into a scratch place on a schedule, checking the data and keys, and timing it.
- Why use a tunnel instead of opening ports, and what do you give up? The server makes only outgoing connections, so no inbound web port faces the internet, and the edge handles TLS and floods. You trust a third party with traffic in clear text at the edge, and the origin must refuse anything that skipped the tunnel.
Related
- /help/technology/testing
- /help/technology/private-edition
- /help/technology/data