Docker for Beginners

Docker for Beginners: Containers, Images, Volumes, Compose

If you’re new to Docker, it can feel like “magic”: you run a single command and suddenly an app works on your machine without installing a bunch of dependencies. 🧙‍♂️

This Docker for Beginners guide explains Docker from zero: what it is, why it matters, how to configure containers safely, how images and volumes work, and how to manage a full stack with Compose — plus the everyday commands you’ll use to run, stop, inspect, and remove things. ✅


What Docker is (in plain terms)

Docker is a tool that lets you package an application together with everything it needs (libraries, runtime, config defaults) into an image. From that image you start a container — a running, isolated process on your system. 📦➡️🏃

Key idea: You don’t install apps directly on the OS; you run them inside containers.

Docker vs Virtual Machines

  • Virtual Machine (VM): ships a whole OS per VM. Heavier, slower to start. 🧱
  • Docker container: shares the host OS kernel and isolates processes. Lighter, fast start. ⚡

Docker is not “more secure by default” than a VM; it’s just a different isolation model. Keep security basics in mind (we’ll cover them). 🔐


Why Docker is useful

  • Reproducible environments: “Works on my machine” becomes “works anywhere that runs Docker.” 🌍
  • Easy upgrades & rollbacks: switch image versions, restart, done. 🔁
  • Clean host OS: dependencies stay inside the container. 🧼
  • Portable stacks: run databases, apps, monitoring tools together via Docker Compose. 🧩

Core concepts you must know

1) Image

A read-only template: app + dependencies + default config.

Examples: nginx:alpine, postgres:16, redis:7.

2) Container

A running instance of an image.

Think: image = blueprint, container = built house. 🏠

3) Registry

A place to store images.

  • Docker Hub (public)
  • GitHub Container Registry
  • Private registries

4) Volume

Persistent storage for containers. Without volumes, data can be lost when containers are removed. 💾

5) Network

A way for containers to talk to each other by service name (especially in Docker Compose). 🌐


Your first Docker commands

Check Docker is working

docker version
docker info

Run a test container

docker run --rm hello-world
  • run starts a container.
  • --rm deletes it automatically after it exits.

Everyday container lifecycle commands

List containers

# Running containers
docker ps

# All containers (including stopped)
docker ps -a

Start / stop / restart

docker stop <container_name_or_id>
docker start <container_name_or_id>
docker restart <container_name_or_id>

Remove containers

# Remove a stopped container
docker rm <container_name_or_id>

# Force-remove a running container
docker rm -f <container_name_or_id>

View logs

docker logs <container_name_or_id>

# Follow logs live
docker logs -f <container_name_or_id>

# Last 200 lines
docker logs --tail 200 <container_name_or_id>

Execute a shell inside a running container

# Try sh first (common on Alpine)
docker exec -it <container_name_or_id> sh

# Or bash (common on Debian/Ubuntu images)
docker exec -it <container_name_or_id> bash

Inspect details (super useful)

docker inspect <container_name_or_id>

# Example: get container IP address (not always needed in Compose)
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container>

Running containers: the most important docker run parameters

1) Naming the container

docker run --name my-nginx nginx:alpine

2) Detached mode (run in background)

docker run -d --name my-nginx nginx:alpine

3) Ports (publish container port to host)

# host_port:container_port
docker run -d --name web -p 8080:80 nginx:alpine
  • Container listens on 80.
  • Your host exposes it on http://localhost:8080. 🌐

4) Environment variables

docker run -d --name app \
  -e NODE_ENV=production \
  -e APP_PORT=3000 \
  my-image:latest

Environment variables are the most common way to configure containers.

5) Volumes: persistent data

Named volume (recommended for most data)

docker volume create pgdata

docker run -d --name postgres \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

Bind mount (map a host folder into container)

docker run -d --name web \
  -p 8080:80 \
  -v /srv/web:/usr/share/nginx/html:ro \
  nginx:alpine
  • Bind mounts are great for dev and for “I want my files on the host.”
  • :ro makes it read-only (safer). 🔒

6) Restart policies (auto-start after reboot)

docker run -d --name app --restart unless-stopped my-image:latest

Common options:

  • no (default)
  • always
  • unless-stopped
  • on-failure

7) Resource limits (avoid one container eating your server)

docker run -d --name heavy \
  --cpus="2" \
  --memory="1g" \
  my-image:latest

8) User / permissions (reduce “root inside container” problems)

# Run as UID:GID (example)
docker run -d --name app \
  --user 1000:1000 \
  -v /srv/appdata:/data \
  my-image:latest

This matters a lot when using bind mounts: the container must have permission to read/write host files. 🧩


Images: build, pull, list, remove

Pull an image

docker pull nginx:alpine

List images

docker images

Remove an image

docker rmi nginx:alpine

Build your own image (Dockerfile basics)

Example Dockerfile (minimal Node.js app):

# Use an official base image
FROM node:20-alpine

# Set working directory
WORKDIR /app

# Copy package files first (better caching)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy the rest of the source code
COPY . .

# Expose app port (documentation purpose)
EXPOSE 3000

# Run the app
CMD ["node", "server.js"]

Build it:

docker build -t my-node-app:1.0 .

Run it:

docker run -d --name myapp -p 3000:3000 my-node-app:1.0

Docker Compose (recommended for real projects)

If docker run is “single container,” Docker Compose is “a stack” (app + database + cache + reverse proxy) defined in a docker-compose.yml file. 🧩

This is the point where Docker for Beginners usually “clicks”: instead of juggling lots of long docker run commands, you keep everything readable and repeatable in one YAML file. 📄✅

Why Compose is better for beginners

  • One file documents your setup
  • Easy to start/stop everything
  • Networks are automatic
  • Containers can refer to each other by service name

Example: app + database

services:
  db:
    image: postgres:16
    container_name: demo_postgres
    environment:
      # Use strong secrets in real setups
      POSTGRES_USER: demo
      POSTGRES_PASSWORD: demo_password
      POSTGRES_DB: demo_db
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped

  app:
    image: nginx:alpine
    container_name: demo_nginx
    ports:
      - "8080:80"
    depends_on:
      - db
    restart: unless-stopped

volumes:
  db_data:

Run:

docker compose up -d

Stop:

docker compose down

List services:

docker compose ps

View logs:

docker compose logs -f

Important Compose fields you’ll use often

  • services: your containers
  • image: which image to use
  • build: build from a Dockerfile instead of pulling an image
  • container_name: optional fixed name (not required, but convenient)
  • ports: host:container port mapping
  • environment: env vars
  • env_file: load variables from a .env file
  • volumes: persistent storage or bind mounts
  • restart: auto-start policy
  • depends_on: start order (not “readiness”)
  • healthcheck: verify container is actually ready
  • networks: custom networks if needed
  • user: run as non-root UID:GID

Volumes: don’t lose your data

A beginner mistake: removing a container and accidentally losing the database. 😬

Rules of thumb:

  • App code/config: bind mount (common in dev)
  • Databases: named volumes (common in prod)
  • Backups: always do backups outside the container

Useful commands:

# List volumes
docker volume ls

# Inspect volume
docker volume inspect <volume_name>

# Remove volume (DANGEROUS: deletes data)
docker volume rm <volume_name>

Networks: how containers talk to each other

In Compose, containers are automatically connected to a default network and can reach each other via service name. For example, your app can connect to Postgres at host db (the service name). 🌐

Commands:

# List networks
docker network ls

# Inspect a network
docker network inspect <network_name>

Healthchecks (the “is it actually ready?” problem)

depends_on ensures start order, but does not guarantee the database is ready to accept connections.

A healthcheck example (Postgres):

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U demo"]
      interval: 10s
      timeout: 5s
      retries: 5

Cleanup: keep Docker from eating disk space

Over time you collect unused images, stopped containers, old networks, and build cache.

Safe-ish cleanup:

# Remove stopped containers, unused networks, unused images
docker system prune

More aggressive (removes unused images too):

docker system prune -a

Remove unused volumes (be careful):

docker volume prune

Troubleshooting checklist

  1. Container keeps restarting 🔁
  • Check logs: docker logs --tail 200 <container>
  • Check exit code: docker inspect -f '{{.State.ExitCode}}' <container>
  1. Port not reachable 🌐
  • Confirm port mapping: docker port <container>
  • Confirm service is listening inside container
  1. Permission denied on volumes 🧷
  • Check UID/GID on host vs container user
  • Prefer --user or images that support PUID/PGID
  1. App can’t reach DB 🔌
  • In Compose, use service name (db), not localhost
  • Verify network: docker network inspect ...

Security basics (beginner-friendly)

  • Avoid --privileged unless you truly need it.
  • Prefer read_only: true where possible.
  • Drop Linux capabilities if you know what you’re doing.
  • Don’t run everything as root; use user: 1000:1000 when feasible.
  • Don’t store secrets in plain text inside docker-compose.yml for public repos.

Quick cheat sheet (copy/paste)

# Containers
docker ps
docker ps -a
docker start <c>
docker stop <c>
docker restart <c>
docker rm <c>
docker rm -f <c>
docker logs -f <c>
docker exec -it <c> sh

docker inspect <c>

# Images
docker pull <image:tag>
docker images
docker rmi <image:tag>
docker build -t my-image:1.0 .

# Volumes
docker volume ls
docker volume inspect <v>
docker volume rm <v>

# Compose
docker compose up -d
docker compose down
docker compose ps
docker compose logs -f

# Cleanup
docker system prune

Official docs (recommended)

Docker documentation: https://docs.docker.com/
Docker Compose documentation: https://docs.docker.com/compose/
Dockerfile reference: https://docs.docker.com/reference/dockerfile/
Docker CLI reference: https://docs.docker.com/reference/cli/docker/

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.