Leaner, Smarter, Production-Ready: A Practical Guide to Multi-Stage Docker Builds and Container…
Multi-stage builds, docker run flags decoded, and the three commands every engineer needs to debug a live container — all in one place.
Leaner, Smarter, Production-Ready: A Practical Guide to Multi-Stage Docker Builds and Container Debugging
Multi-stage builds, docker run flags decoded, and the three commands every engineer needs to debug a live container — all in one place.
The Problem With Single-Stage Builds
A standard Dockerfile does everything in one go — install build tools, compile the app, copy source files, set the entrypoint. It works. But the image it produces carries the dead weight of every tool used during the build: compilers, test runners, dev dependencies, intermediate files. None of that belongs in production.
A React app built with a standard single-stage Dockerfile can easily produce an image over 1GB. The actual output you need to serve — the compiled static files — is a few megabytes at most.
A build tool that weighs 400MB has no business riding into production inside your image.
This is the problem multi-stage builds solve.
Multi-Stage Builds: What Changes
Multi-stage builds let you use multiple FROM statements in a single Dockerfile. Each FROM starts a fresh stage with its own isolated filesystem. You compile and build in an early stage, then copy only the finished output into a clean, minimal final stage. The intermediate stages — with all their build tools and temporary files — never make it into the final image.
The result is a production image that contains only what is needed to run the app. Nothing more.
The Dockerfile
# ── Stage 1: build the app ───────────────────────────
FROM node:18-alpine AS installer
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# ── Stage 2: serve with nginx ────────────────────────
FROM nginx:latest AS deployer
COPY --from=installer /app/build /usr/share/nginx/html
What each part does
Stage 1 — installer
The AS installer label names this stage so it can be referenced later. This stage uses the full node:18-alpine image to install dependencies and run the build. Everything produced by npm run build lands in /app/build. This stage is heavy — and deliberately so, because it's doing real work.
Stage 2 — deployer
This stage starts completely fresh from nginx:latest — a lightweight web server image. The single COPY --from=installer instruction copies only the compiled output from Stage 1. No node_modules, no source files, no build toolchain. The final image contains nginx plus your static files, and nothing else.
The size difference in practice:
Approach What’s included Typical image size Single-stage (node base) Full Node.js runtime + all deps + source 800MB — 1.2GB Multi-stage (nginx final) nginx + compiled static output only 20MB — 50MB
docker run — Every Flag Decoded
Once the image is built, you launch it with docker run. The flags you pass determine how the container behaves. Let's break down a common command:
docker run -it -dp 3000:3000 multi-stage
docker run
The base command. Tells Docker to create a new container from the specified image and start it. If the image isn’t found locally, Docker automatically pulls it from Docker Hub.
-i — Interactive (keep stdin open)
Keeps the standard input stream open so you can type commands into the container. Without -i, the container cannot receive keyboard input — useful when running a shell session.
-t — Allocate a TTY terminal
Allocates a pseudo-terminal (TTY) inside the container. This gives you a proper shell experience with a visible prompt, line editing, and colour output — similar to SSH-ing into a remote server.
*-iand-tare almost always used together as-it. One opens the pipe; the other makes it feel like a real terminal.*
-d — Detached mode (background)
Runs the container in the background and immediately returns your terminal. Docker prints the container ID and you’re back at your prompt — the container continues running silently.
-p 3000:3000 — Port mapping
Maps a port on your host machine to a port inside the container. The format is host_port:container_port.
Without -p, your application runs inside the container but is completely unreachable from the outside. With -p 3000:3000, any request to localhost:3000 on your machine is forwarded into port 3000 inside the container.
You can map different ports if needed:
docker run -dp 8080:3000 multi-stage
# host port 8080 → container port 3000
# access at localhost:8080
multi-stage — The image name
The name (or name:tag) of the image to run. Docker checks its local image store first. If not found, it pulls from Docker Hub. You can also use the full image path for private registries:
docker run -dp 3000:3000 your-username/multi-stage:v1.0
-it vs -d — Pick one
These flags serve different purposes and are rarely combined intentionally:

The Three Debugging Commands
Once a container is running, these three commands are your primary tools for understanding what’s happening inside.
docker logs — Read the container's output
Every container writes its standard output and standard error to a log stream. docker logs reads it.
# View all logs from a container
docker logs container-name
# Or using the container ID
docker logs container-id
Useful flags:
# Follow logs in real time (like tail -f)
docker logs -f container-name
# Show only the last 50 lines
docker logs --tail 50 container-name
# Show logs with timestamps
docker logs -t container-name
# Combine: follow the last 20 lines with timestamps
docker logs -ft --tail 20 container-name
This is always the first place to look when a container misbehaves. Crash on startup? Check the logs. App throwing errors? Check the logs. Port not responding? Check the logs.
docker exec — Run commands inside a running container
docker exec lets you execute any command inside a container that is already running — without stopping or restarting it.
# Open an interactive shell (most common use)
docker exec -it container-name sh
# Or with bash (if the image includes it)
docker exec -it container-name bash
# Run a single command without entering a shell
docker exec container-name ls /app
docker exec container-name env
docker exec container-name cat /etc/nginx/nginx.conf
Once inside with sh or bash, you can:
- Inspect the file system to verify your code was copied correctly
- Check environment variables with
envorprintenv - Test network connectivity with
ping,curl, orwget - Examine running processes with
ps aux - Read config files your app is using
Type exit to leave the container shell without stopping the container.
***execvsrun -it:*docker run -it image shcreates a new container and opens a shell in it.docker exec -it container shenters a container that is already running. For debugging a live deployment, you almost always wantexec.
docker inspect — Full container metadata
docker inspect returns a detailed JSON object describing every aspect of a container's configuration and current state.
docker inspect container-name
# or
docker inspect container-id
The output is verbose, but you can filter for exactly what you need using --format with Go template syntax:
# Get the container's IP address
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name
# Check port bindings
docker inspect --format '{{json .NetworkSettings.Ports}}' container-name
# See all environment variables
docker inspect --format '{{range .Config.Env}}{{.}}
{{end}}' container-name
# Check the container's restart policy
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' container-name
# See the mounted volumes
docker inspect --format '{{json .Mounts}}' container-name
docker inspect is particularly useful for:
- Debugging networking issues (wrong IP, port not bound)
- Verifying environment variables were passed correctly
- Checking whether volumes are mounted where you expect
- Diagnosing restart policy and exit code issues
Putting It All Together — A Debugging Workflow
Here is a typical sequence when something goes wrong in a running container:
# 1. Check if the container is running at all
docker ps
# 2. If it's not in the list, check stopped containers too
docker ps -a
# 3. Read the logs — most errors surface here first
docker logs -f my-container
# 4. If the app started but behaves unexpectedly, exec in to investigate
docker exec -it my-container sh
# 5. Inside the container: verify files, env vars, network
ls /app
env
cat /app/config.json
# 6. Exit the shell
exit
# 7. If you need low-level config details (ports, mounts, networking)
docker inspect my-container
Quick Reference
Multi-stage Dockerfile pattern
# Stage 1: build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: production
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
Key commands
docker run -dp 3000:3000 image — Run container detached with port mapping docker run -it image sh — Start a new container with an interactive shell docker logs container — View stdout/stderr from the container docker logs -f container — Follow logs in real time docker exec -it container sh — Shell into a running container docker exec container env — Print environment variables docker inspect container — Full JSON metadata of a container docker inspect — format ‘…’ container — Extract a specific field docker ps — List running containers docker ps -a — List all containers including stopped ones
docker run flags
-i / — interactive — Keep stdin open -t / — tty — Allocate a pseudo-terminal -d / — detach — Run in the background -p / — publish — Map host port to container port — name — Assign a name to the container -e / — env — Set an environment variable -v / — volume — Mount a host directory into the container — rm — Automatically remove the container when it exits
Build lean. Debug fast. Ship with confidence.
메타데이터
- post_id
- 0135fa976b2f
- slug
- leaner-smarter-production-ready-a-practical-guide-to-multi-stage-docker-builds-and-container-0135fa976b2f
- url
- https://medium.com/@gunjankapoor9999/leaner-smarter-production-ready-a-practical-guide-to-multi-stage-docker-builds-and-container-0135fa976b2f
- canonical_url
- https://medium.com/@gunjankapoor9999/leaner-smarter-production-ready-a-practical-guide-to-multi-stage-docker-builds-and-container-0135fa976b2f
- author_url
- https://medium.com/@gunjankapoor9999
- status
- ok
- fetched_at
- 2026-06-09 15:37:30