Skip to content
DevOps TRENDING

Docker for Beginners: From Your First Container to Production

Images, containers, Dockerfiles, volumes and Compose — explained step by step, with the habits that make containers safe to run in production.

Rahul Verma4 min read
A multi-stage Dockerfile next to a grid of running containers for web, API, worker, database, cache and proxy
On this page

“It works on my machine” is the problem Docker was built to solve. A container packages your application together with its runtime and dependencies, so it behaves the same on a laptop, a CI runner and a production server. This guide takes you from your first docker run to an image you would be comfortable deploying.

Images and containers in one sentence each

  • An image is a read-only template: a filesystem plus metadata such as the default command.
  • A container is a running instance of an image, with its own isolated process space, network and writable layer.

You build images once and run many containers from them.

Your first container

After installing Docker Desktop (or Docker Engine on Linux), run:

docker run --rm -p 8080:80 nginx:1.27-alpine

Open http://localhost:8080 and you will see the Nginx welcome page. Breaking the command down:

  • --rm removes the container when it stops.
  • -p 8080:80 maps port 8080 on your machine to port 80 inside the container.
  • nginx:1.27-alpine is the image name and a specific tag. Pinning a tag avoids surprises when latest changes.

Useful companions: docker ps lists running containers, docker logs <id> shows output and docker exec -it <id> sh opens a shell inside a container.

Writing a Dockerfile

Here is a Dockerfile for a small Node.js API. It uses a multi-stage build: the first stage installs dependencies and builds; the final stage contains only what is needed to run.

# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

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

Build and run it:

docker build -t my-api:1.0.0 .
docker run --rm -p 3000:3000 my-api:1.0.0

Why the order of instructions matters

Docker caches each instruction as a layer. Copying package*.json and installing dependencies before copying the rest of the source means that editing a source file does not invalidate the dependency layer. Rebuilds become much faster.

Add a .dockerignore file so local clutter never reaches the image:

node_modules
.git
.env
dist
coverage

Keeping data: volumes

Containers are disposable. Anything written inside a container’s filesystem disappears when the container is removed. Databases and uploads need volumes:

docker volume create pgdata
docker run -d --name db -e POSTGRES_PASSWORD=devpass \
  -v pgdata:/var/lib/postgresql/data postgres:16

In development you can also bind-mount your source directory so changes appear instantly, but avoid bind mounts in production.

Multiple services with Docker Compose

Real applications are rarely one container. Compose describes several services in one file:

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:devpass@db:5432/app
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: devpass
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 10
volumes:
  pgdata:

docker compose up -d starts everything; services reach each other by service name (db), because Compose creates a private network.

The passwords above are for local development only. In production, inject secrets from your platform’s secret manager — never bake them into images or commit them to Git.

From laptop to production: the habits that matter

  • Pin versions of base images and rebuild regularly to pick up security patches.
  • Run as a non-root user (USER node above) to limit the impact of a compromise.
  • Keep images small: alpine or slim bases, multi-stage builds, no build tools in the final stage.
  • One process per container, logging to stdout/stderr so the platform can collect logs.
  • Add health checks so orchestrators can restart unhealthy containers.
  • Scan images for known vulnerabilities in CI before pushing to a registry.
  • Tag images immutably (for example with the Git commit SHA) so every deployment is traceable.

Debugging containers: a short toolkit

When something does not work, these commands answer most questions:

docker ps -a                      # is the container running, or did it exit?
docker logs -f api                # what did the process print?
docker inspect api | less         # env vars, mounts, network settings
docker exec -it api sh            # look around inside the container
docker stats                      # live CPU and memory usage
docker image history my-api:1.0.0 # which layers make the image large?

A container that exits immediately usually means the main process crashed or finished — read the logs first. “Connection refused” between services often means you used localhost instead of the service name: inside a container, localhost is the container itself, not your machine.

Finally, clean up occasionally. Unused images, stopped containers and build cache can take gigabytes of disk space. docker system df shows usage and docker system prune removes unused data (read its prompt carefully before confirming).

Where to go next

Once you are comfortable with images and Compose, the natural next steps are pushing images to a registry from CI, and running them on a managed platform or an orchestrator such as Kubernetes. The fundamentals you learned here — layers, volumes, networks and small, non-root images — carry over directly.

Written by

Rahul Verma

DevOps & cloud · containers and CI/CD

Writes hands-on guides to containers, cloud services and the automation that ships software safely. (Demo author profile.)

Comments

Join the discussion

Your email address will not be published. Be kind and stay on topic.