This episode wraps the WebSocket server in a container: a Dockerfile with a multi-stage build, Node.js best practices, docker networking and port mapping, Docker Compose with Redis, and container health checks.

A WebSocket server running directly on a machine is a time bomb: configuration depends on the machine, Node.js versions differ between environments, and dependencies can break. Containerization locks the whole environment into one image that runs anywhere.
Episode 27 covers Docker & containerization: writing a Dockerfile for the WebSocket server, using a multi-stage build, understanding docker networking, wiring up Redis and the server with Docker Compose, and adding health checks.
Start with the official Node.js image and the right structure.
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN npm install --omit=dev
COPY server.js ./
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/server.js ./server.js
EXPOSE 8080
USER node
CMD ["node", "server.js"]node:22-alpine produces a small, light image. USER node runs the application as a non-root user — an important security practice. The first layer installs dependencies, the second only copies what is needed.
FROM ... AS builder followed by COPY --from=builder forms a multi-stage build: the first stage installs dependencies, the second produces a lean final image. The production image carries no unnecessary build tools.
Containers have their own network namespace. Expose ports to the host with mapping.
docker run -d --name ws-server -p 8080:8080 ws-server:latest-p 8080:8080 maps host port 8080 to container port 8080. Without mapping, the port inside the container is unreachable from outside.
Use a dedicated network so containers communicate safely without exposing ports publicly.
docker network create ws-netWith docker network create ws-net and connecting containers to that network, the server can reach Redis via the service name without mapping the Redis port to the host — only the WebSocket port is open to the outside.
Docker Compose wires all services together in one file.
services:
ws-server:
build: .
ports:
- "8080:8080"
environment:
REDIS_URL: redis://redis:6379
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:8080/healthz').then(r => process.exit(r.ok ? 0 : 1))"]
interval: 10s
timeout: 5s
retries: 3
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3sdepends_on with condition: service_healthy ensures the server waits for Redis to be healthy before starting. The variable REDIS_URL: redis://redis:6379 uses the service name, not an IP — Compose's internal DNS handles it.
docker compose up -ddocker compose up -d builds the image, creates the network, and runs all services. One command brings a full production environment to a local machine.
A health check distinguishes "the container is alive" from "the application works". The /healthz endpoint from episode 15 is the basis of this.
http.createServer((req, res) => {
if (req.url === "/healthz") {
const sehat = wss.clients.size < 5000;
res.writeHead(sehat ? 200 : 503);
res.end(sehat ? "ok" : "penuh");
return;
}
res.writeHead(404);
res.end();
}).listen(8080);wss.clients.size < 5000 makes the health check reflect real capacity: the server responds 503 when nearly full, so the orchestrator knows when to add instances.
In Kubernetes (episode 28), these two probes differ: liveness checks whether the app is still alive (restart on failure), readiness checks whether it is ready to accept traffic (stop traffic on failure). In Compose, a single healthcheck is enough for most cases.
mem_limit and cpu_limit.Episode 27 turned a fragile WebSocket server into a portable image: locked dependencies, an isolated environment, and a complete stack (server plus Redis) running with a single command.
Key takeaways:
In the next episode we bring the stack to orchestration: Kubernetes deployment — Deployment, Service, Ingress with session affinity, HPA, and service mesh.