You know what deployment is — taking your code and putting it somewhere real people can reach it. But when you watch it happen for the first time, it can feel like a black box: you run one command, wait a bit, and your changes magically appear on a live website. So what’s actually going on inside that pause? It turns out deployment isn’t one mysterious leap. It’s a sequence of small, sensible steps, and once you can name them, the whole thing stops being scary.
Here’s the reassuring part: those steps are nearly always the same, no matter what tool or platform you use. A push-button cloud service and a hand-typed deploy on a bare server both walk through the same logical stages — they just hide more or less of it from you. Learn the stages once and you’ll recognize them everywhere.
Deployment is a pipeline, not a moment
It’s tempting to picture deployment as a single instant: code is “here” one second and “live” the next. A more accurate picture is a pipeline — a line of steps your code flows through, each one finishing before the next begins. Some steps prepare the code, some move it, some get the server ready, and the last few flip the switch and make sure nothing’s on fire.
Thinking in stages matters for a practical reason: when a deployment fails, it fails at one specific stage. The build broke, or the files didn’t transfer, or the database migration errored out. If you know the stages, a scary “deployment failed” message becomes a much smaller question — which step? — and that’s almost always answerable.
If you haven’t yet, it helps to be clear on the gap deployment is crossing in the first place: the difference between code running on your machine and code running for everyone is covered in localhost vs production. Deployment is the bridge between those two worlds, and the stages below are the planks of that bridge.
The universal stages
Almost every deployment, from the simplest to the most elaborate, works through some version of these stages. Not every project uses all of them, but this is the full menu.
SOURCE BUILD PACKAGE TRANSFER
─────── ──► ──────── ──► ───────── ──► ─────────
your code compile / bundle it send to the
+ assets bundle up neatly server
│
▼
VERIFY ◄── SWITCH ◄── MIGRATE ◄── INSTALL
──────── ─────── ───────── ──────────
is it up point traffic update the install deps,
and healthy? at the new database set up runtime
version if needed
Let’s walk through each one in plain language.
1. Build: turn source into something runnable
The code you write isn’t always the code that runs. Building is the step that transforms your human-friendly source into the form the server actually executes. What this means depends on your stack: a frontend project might bundle dozens of files into a few optimized ones, a TypeScript project compiles down to JavaScript, a compiled language turns source into a binary. Even a “no build needed” project conceptually has an empty build step.
The important idea: building happens before anything goes near the server, and it can fail. A typo that the server would never even see can stop a deploy dead here — which is exactly what you want. Catching problems at build time is far cheaper than catching them live.
# A typical build step for a web project
npm install # get the libraries the code depends on
npm run build # produce the optimized, runnable output
2. Package: bundle the result neatly
Once you have built output, you usually gather it into one tidy unit — sometimes called an artifact. This might be a zip file, a compiled binary, or a container image. The goal is a single, self-contained thing you can move around and trust: this exact bundle is what will run, nothing more, nothing less.
Packaging is what makes deployments repeatable. If you build once and deploy the same artifact to staging and then production, you know the thing you tested is the thing that went live. Building separately for each environment invites the classic “but it worked in staging” mystery.
3. Transfer: get it onto the server
Now the bundle has to physically travel from wherever it was built (your laptop, or a build server) to the machine that will run it. Under the hood this is just copying files across a network — securely. The most universal way is over an encrypted connection.
# Copy a built bundle to a server over a secure connection
scp app.tar.gz user@your-server:/var/www/app/
You don’t always type this yourself. A platform might pull your code from a repository, or a build server might push the artifact for you. But conceptually, something always crosses the gap from “where it was built” to “where it will run.”
4. Install: prepare the runtime on the server
Code rarely runs alone. It needs its dependencies — the libraries it imports — and a runtime to execute it (a language interpreter, a virtual machine, and so on). The install stage makes sure the server has all of that in place and at the right versions. On the server, this often looks similar to what you ran locally, just aimed at the production machine.
This is also where servers and laptops diverge in ways that bite people. The version of a tool on your machine might differ from the one on the server; a library that’s “obviously installed” locally might be missing in production. Installing dependencies explicitly, from a locked list, is how teams keep the two in sync.
5. Migrate: bring the database up to date
If your app uses a database, the new code might expect a new shape of data — a new column, a new table, a renamed field. A database migration is a small, ordered script that makes those structural changes so the database matches what the new code expects. Migrations run as part of the deploy, before the new code starts handling real traffic.
Migrations are the riskiest step — treat them with respect
Most deployment steps are safe to undo: if the new code misbehaves, you put the old code back. Database changes are different. Drop a column and the data in it is gone — rolling back the code won’t bring it back. That’s why careful teams write migrations to be additive when possible (add the new column, fill it, switch over, remove the old one later in a separate deploy), and always back up before a risky change. When a deployment goes badly wrong, it’s usually the migration, not the code.
6. Switch: point traffic at the new version
Up to now, the old version of your app has been quietly serving users the whole time. The switch is the moment the server starts using the new version instead. How abrupt this is depends on your setup. The simplest approach stops the old version and starts the new one — quick, but there’s a blink where the app is down. More careful setups start the new version alongside the old, confirm it’s healthy, and only then redirect traffic to it, so users never see an interruption.
BEFORE SWITCH AFTER SWITCH
───────────── ────────────
traffic ──► v1 (old) traffic ──► v2 (new)
v2 (warming) v1 (kept briefly,
then shut down)
That second pattern — start new, prove it works, then flip — is the heart of what people mean by “zero-downtime” deploys. The old version is often kept running for a few minutes so you can flip back fast if the new one misbehaves.
7. Verify: confirm it actually works
Deploying isn’t done when the new version starts — it’s done when you’ve confirmed it’s healthy. Verification can be as simple as opening the site and clicking around, or as automated as a health check: the server (or a monitoring tool) hits a special URL that answers “yes, I’m alive and working.” If that check fails, a good pipeline can automatically roll back to the last known-good version instead of leaving a broken app live.
# A health check is often just a tiny endpoint that returns "OK"
curl https://your-app.example.com/health
# → {"status":"ok"}
Skipping verification is how a “successful” deploy quietly takes a site down for an hour before anyone notices. The deploy ran; nobody checked. Always close the loop.
A worked example, start to finish
Let’s tie it together with a concrete-but-generic story. The team at ACY Partner Indonesia has a web app and Jane Doe just finished a feature. Here’s what happens when she deploys it.
1. BUILD Jane's code is compiled and bundled into optimized files.
A leftover typo would fail here — it doesn't. ✓
2. PACKAGE The output is gathered into one artifact (app-v2.tar.gz).
3. TRANSFER The artifact is copied to the production server over SSH.
4. INSTALL On the server, dependencies are installed from the locked
list, matching exactly what was tested.
5. MIGRATE One small migration adds a "last_login" column. Backed up
first, just in case.
6. SWITCH The new version starts alongside the old. Once it answers
healthy, traffic flips to it. The old version lingers 5 min.
7. VERIFY A health check passes and Jane loads the site herself.
Green across the board. The old version is shut down.
Notice what didn’t happen: nobody emailed files around, nobody edited code directly on the live server, and at no point was the app fully down with no fallback. That discipline — repeatable artifact, ordered steps, a way back — is what separates a calm deploy from a stressful one.
How automation runs these steps for you
Doing all of that by hand, every time, would be tedious and error-prone — and humans skip steps when they’re tired. So teams automate the pipeline. You connect your code repository to a system that watches for changes, and when you push new code, it runs the stages above automatically: build, test, package, deploy, verify. This is the idea behind CI/CD (continuous integration / continuous delivery).
you push code
│
▼
┌──────────────────────────────────────────┐
│ AUTOMATED PIPELINE │
│ build → test → package → deploy → verify │
└──────────────────────────────────────────┘
│
▼
live, or auto-rolled-back if a step fails
The crucial mental shift: automation doesn’t replace the stages, it runs the same stages reliably. Every concept above still applies. The pipeline is just a tireless assistant that performs the exact sequence the same way every time, refuses to continue if a step fails, and can undo its own work when verification doesn’t pass. That’s why “it works on my machine” matters less on a good pipeline — the machine that builds and tests is the same one every time, not your laptop.
The same stages hide inside one-click platforms
Modern hosting platforms can make deployment feel like a single button or a single git push. That’s a lovely experience, but it’s worth knowing it’s not magic — behind that button, the platform is running the very stages in this article. When something goes wrong, the platform’s logs will mention a build failing, an install erroring, a health check timing out. You already know what those words mean, so you’ll know where to look.
Rollbacks: the undo button
A pipeline that can deploy should also be able to un-deploy. A rollback is returning to the previous working version when a new one turns out to be broken. Because you packaged each version as its own artifact and kept the old one around, rolling back is often as quick as pointing traffic at the previous version again — the switch step, run in reverse.
This is exactly why the careful patterns earlier pay off. Keeping the old version alive for a few minutes, writing additive migrations, building immutable artifacts — none of that is busywork. It’s all in service of one calm sentence you want to be able to say when a deploy goes wrong: “no problem, roll it back.” A deployment process you can’t undo is a process that turns small mistakes into long outages.
Why understanding the stages helps you
You might be using a platform that handles all of this for you today, and that’s great. But understanding the stages still pays off constantly. When a deploy fails, you’ll read the error and immediately know it’s a build problem versus a migration problem versus a health-check problem — and that tells you where to fix it. When you choose tools, you’ll understand what they’re actually automating. And when you eventually deploy something the hard way, on a plain server, the steps won’t be a surprise, because you’ve seen the shape of them here.
Deployment stops being a black box the moment you can name what’s inside it. It’s not one leap — it’s build, package, transfer, install, migrate, switch, verify, with a rollback waiting in case you need it. Every tool you’ll ever use is some arrangement of those same ideas.
Wrapping up
Here’s the whole thing in one place:
- Deployment is a pipeline, not a moment — code flows through ordered stages, and failures happen at one identifiable stage.
- The universal stages are: build (source → runnable), package (one tidy artifact), transfer (move it to the server), install (dependencies + runtime), migrate (update the database), switch (point traffic at the new version), and verify (confirm it’s healthy).
- Migrations are the riskiest stage because data changes can’t always be undone — back up and prefer additive changes.
- Zero-downtime comes from starting the new version alongside the old, proving it healthy, then flipping traffic.
- Automation (CI/CD) doesn’t replace these stages — it runs the same stages reliably every time, and can auto-rollback when a step fails.
- A rollback is the undo button, and it only works if your process was built to allow it.
Next, it’s worth seeing how the stages differ depending on what you’re shipping — a folder of static files behaves very differently from a long-running server process, and knowing which one you have changes nearly every step above.