DEV Community

Cover image for Docker, Explained Through the Problem It Solves
Rizky Haksono
Rizky Haksono

Posted on Edited on

Docker, Explained Through the Problem It Solves

The first useful thing Docker gave me was not “cloud-native architecture.” It was a boring promise: the application should behave the same way on another machine.

Before containers, a setup could depend on the exact Node version, system packages, environment variables, or a database installed on one laptop. Docker makes those assumptions explicit and packages the application process with its runtime dependencies.

Image, container, and volume

These three words explain most day-to-day Docker work:

  • An image is an immutable template built from a Dockerfile.
  • A container is a running instance of that image.
  • A volume stores data that should survive when a container is replaced.

A container is not a tiny virtual machine. Containers share the host kernel, which is one reason they usually start faster and use fewer resources than full VMs.

A small Node example

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Build and run it:

docker build -t notes-api .
docker run --rm -p 3000:3000 --env-file .env notes-api
Enter fullscreen mode Exit fullscreen mode

The port mapping exposes container port 3000 on the host. The environment file supplies configuration at runtime instead of baking secrets into the image.

Where Docker helps

Docker is useful when I need:

  • reproducible local environments;
  • isolated services such as Postgres or Redis;
  • the same artifact in CI and deployment;
  • an explicit record of system dependencies.

It does not automatically make an application scalable or secure. A large image is still large. A process running as root is still risky. A stateful service still needs backups.

A few habits that prevent pain

Use a .dockerignore file, pin important base-image versions, run as a non-root user, keep secrets outside the image, and rebuild images instead of manually changing running containers.

For local multi-service work, Compose is often enough:

services:
  app:
    build: .
    ports: ["3000:3000"]
    env_file: .env
  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_PASSWORD: local-only
Enter fullscreen mode Exit fullscreen mode

The mental model I keep

Docker packages a process and its dependencies behind a repeatable boundary. That boundary is valuable, but it is not magic. You still need to understand networking, storage, configuration, and the application itself.

Top comments (0)