DevOps · Containers

Docker

Docker packages your application and everything it needs into an image that runs the same way on your laptop, a CI server and the cloud.

Intermediate 5 min Lesson 13 of 30
Before you start:Introduction to DevOps
0/30 in DevOps
On this page · 5 min · Intermediate

Learning objectives#

  • Distinguish images from containers
  • Write a Dockerfile
  • Use multi-stage builds to keep images small
  • Run a container with ports and environment variables

Explanation#

An image is a read-only template: an operating system layer, a runtime, your code and its dependencies. A container is a running instance of an image.

A Dockerfile lists the steps to build an image. Each instruction creates a cached layer, so order instructions from least to most frequently changing.

Multi-stage builds use one stage to build and another, smaller stage to run. Build tools never reach the final image.

Visual explanation#

docker build turns a Dockerfile into an image; docker run starts containers from the imageDockerfileFROM node:22COPY . .RUN npm ciCMD [node…]buildapp codenpm packagesnode runtimealpine linuxImage (read-only layers)runContainer 1:3000 → runningContainer 2:3001 → running
docker build turns a Dockerfile into a layered image; docker run starts containers from it.

Code example: Multi-stage Dockerfile for a Node.js API#

DockerfileDockerfile Illustrative
# ---- build stage ----
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --if-present


# ---- runtime stage ----
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app ./
USER node
EXPOSE 3000
CMD ["node", "server.js"]


# Build and run:
#   docker build -t techie-api .
#   docker run --rm -p 3000:3000 -e JWT_SECRET=change-me techie-api
Expected output
$ docker run --rm -p 3000:3000 techie-api
API running on http://localhost:3000

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. The build stage installs all dependencies with npm ci (reproducible, uses the lockfile) and builds the app.
  2. package*.json is copied before the rest of the code so the dependency layer is cached when only code changes.
  3. The runtime stage installs only production dependencies and copies the built app from the build stage.
  4. USER node runs the process as a non-root user.
  5. -p 3000:3000 maps the host port to the container port; -e passes environment variables.

Real-world use case#

Consistent local development, CI pipelines that build once and deploy the same image everywhere, and hosting on Azure Container Apps, AWS ECS or Kubernetes.

Common mistakes#

Copying node_modules and secrets into the image

Add a .dockerignore with node_modules, .env, .git and build output.

Running as root

If the app is compromised, root inside the container makes escape and damage easier. Use a non-root user.

Using the latest tag in production

latest changes over time. Pin base images and tag your images with versions or commit SHAs.

Best practices#

  • Use multi-stage builds and small base images
  • Add a .dockerignore
  • Pin base image versions
  • Pass configuration through environment variables
  • Scan images for vulnerabilities in CI

Remember this#

Image = recipe output; container = running copy.

Same image runs the same everywhere.

Dockerfile → docker build → docker run.

Practice exercise#

Your task: Write a .dockerignore for the Node.js project above.

Show solution
Text Illustrative
node_modules
npm-debug.log
.env
.git
dist
coverage

Mini quiz#

Docker quizMultiple choice Question 1 of 1

What is the relationship between an image and a container?

Score so far: 0/0

Want more? Practise all DevOps MCQs · DevOps interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.