Containerization and Orchestration: A Small Lab You Can Run
Containerization is only confusing when the example is too big. If you keep the stack small, the ideas become obvious: build an image, run it, confirm it responds. Orchestration at this scale is just a repeatable run command written down so you don’t have to remember it.
This post is a start‑to‑finish lab. Every file and command is real and copy‑paste ready. If you follow it exactly, you will end with a running container, started through Compose, responding to a health check. No placeholders. No skipped steps. No abstract explanation without a working result.
What you are building
You will create a tiny Node web service with one health endpoint, package it into a Docker image, and run it using Docker Compose. The goal is not to build an app. The goal is to understand the minimum moving parts of containerization and a simple orchestration layer.
Before touching anything, here is the end state so the steps make sense.
| Item | Path | Purpose |
|---|---|---|
| Web service | app/index.js | Simple HTTP server with /health endpoint |
| App manifest | app/package.json | Declares dependencies and start command |
| Container build file | app/Dockerfile | Defines how the image is built |
| Orchestration file | docker-compose.yml | Runs the container with a mapped port |
Once these exist, you will be able to build the image, run the container, and confirm it responds.
Prerequisites
You need Docker Desktop installed and Node.js available on your machine. No other tooling is required. If Docker is not installed, install Docker Desktop first and confirm this works before continuing:
docker version
If that command returns a version, Docker is ready.
Step 1: Create the web service
The service will expose one route: /health. This keeps verification simple. If /health returns JSON, the container is working.
Run the following commands to create the app folder and files.
mkdir -p appcat > app/index.js <<'EOF'const express = require("express");const app = express();app.get("/health", (_req, res) => {res.json({ ok: true });});app.listen(3000, () => {console.log("server on 3000");});EOF
Now create the package manifest.
cat > app/package.json <<'EOF'{"name": "lab-app","version": "1.0.0","main": "index.js","scripts": {"start": "node index.js"},"dependencies": {"express": "^4.19.2"}}EOF
Install dependencies and verify the service runs locally.
cd appnpm installnpm start
Expected output:
server on 3000
In another terminal, verify the health endpoint.
curl http://localhost:3000/health
Expected response:
{"ok":true}
Stop the local server with Ctrl+C. At this point the service works outside a container.
Step 2: Build a container image
Now you will package the service into a container image using a Dockerfile.
Create the Dockerfile.
cat > app/Dockerfile <<'EOF'FROM node:18-alpineWORKDIR /appCOPY package.json package-lock.json ./RUN npm install --productionCOPY . .EXPOSE 3000CMD ["npm", "start"]EOF
Build the image.
docker build -t lab-app ./app
If the build completes without errors, the image is ready. You can verify it exists with:
docker images | grep lab-app
Step 3: Run the image with Compose
Running containers manually is fine once. Orchestration starts when the run command becomes a file you can repeat. Docker Compose provides that layer.
Create the Compose file in the project root.
cat > docker-compose.yml <<'EOF'services:app:build: ./appports:- "3000:3000"EOF
Start the service through Compose.
docker compose up --build
You should see output showing the image building and the server starting.
server on 3000
In another terminal, verify the running container.
curl http://localhost:3000/health
Expected response:
{"ok":true}
Stop Compose with Ctrl+C when finished.
What just happened
A Node service was created. A Docker image packaged it. Compose ran the image with a mapped port. The /health endpoint confirmed the container responded correctly. That is the complete containerization and basic orchestration loop in its smallest useful form.
Common failure points
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot find module express | Dependencies not installed | Run npm install in app and rebuild image |
| Container exits immediately | Start command missing | Ensure scripts.start exists in package.json |
curl connection refused | Port mapping missing or container not running | Confirm 3000:3000 in compose and container logs |
| Docker build fails on lockfile | package-lock.json missing | Run npm install before building image |
Closing
Containerization is just packaging a working service into an image. Orchestration is just running that image through a repeatable configuration. Keeping the example small removes the noise and makes the mechanics clear.
Once this lab runs on your machine, you have a real baseline. From here you can add databases, reverse proxies, or multiple services, but the core loop remains the same.