Getting Started with Docker – A Complete Beginner's Guide

Docker has transformed how software is developed, tested, and deployed. If you are new to the concept, this guide walks you through everything from the fundamentals to running your first containerized application.

The Problem Docker Solves

Before Docker, setting up a development environment was painful. You would install Node.js, PostgreSQL, Redis, and a dozen other dependencies on your machine. Your colleague on a Mac might have different versions. The production server ran a different Linux distribution. “It works on my machine” became the most repeated phrase in software teams.

Virtual machines helped by providing isolated environments, but they were heavy. Each VM runs a full operating system, consuming gigabytes of disk space and memory. Starting one takes minutes.

Docker takes a different approach. Instead of virtualizing hardware and running a full OS, it shares the host kernel and isolates only the application and its dependencies. The result is something much lighter: containers start in seconds, use minimal resources, and behave consistently everywhere.

Understanding the Core Concepts

There are four key concepts to understand before diving in:

Image: Think of an image as a snapshot or template. It contains the application code, the runtime, system libraries, and any other files the application needs. Images are read-only — you build them once and run them anywhere. They are composed of layers, where each instruction in the build process creates a new layer on top of the previous one. Layers are cached, so rebuilding an image after a small code change is fast.

Container: A container is a running instance of an image. You can start multiple containers from the same image, and each one runs in isolation. Containers have their own file system, networking, and process space. When you stop a container, all changes made inside it are lost unless you explicitly persist them to a volume.

Dockerfile: This is a text file containing instructions for building an image. Each line is a command that Docker executes in order. The most common instructions are FROM (specify the base image), COPY (copy files into the image), RUN (execute commands during build), and CMD (specify the default command when the container starts).

Docker Compose: Modern applications rarely consist of a single service. Your frontend might depend on an API server, which depends on a database, which might need a message queue. Docker Compose lets you define all these services in a single YAML file and manage them as a group. One command starts everything.

Installing Docker

Docker Desktop is available for Windows, macOS, and Linux. Download the installer from the official Docker website and follow the setup wizard. On Linux, you can also install Docker Engine directly through your package manager. After installation, verify it works by opening a terminal and running:

1
2
docker --version
docker run hello-world

The hello-world command pulls a tiny test image and runs it. If you see a welcome message, Docker is set up correctly.

Your First Dockerfile

Let us create a simple Node.js application and containerize it step by step. Start with a basic Express server:

1
2
3
4
5
6
7
8
9
10
11
12
// server.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
res.json({ message: 'Hello from Docker!' });
});

app.listen(port, () => {
console.log(`Server running on port ${port}`);
});

Next, create a package.json:

1
2
3
4
5
6
7
{
"name": "docker-demo",
"version": "1.0.0",
"main": "server.js",
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^4.18.0" }
}

Now the Dockerfile:

1
2
3
4
5
6
7
8
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]

Let me explain each instruction. FROM node:18-alpine starts from a lightweight Node.js image based on Alpine Linux, which is only about 5MB. WORKDIR /app creates and switches to /app inside the image. COPY package*.json ./ copies only the package files first — this is intentional. We then run npm ci --only=production to install dependencies. Because Docker caches each layer, copying package files separately means the install step is only re-run when dependencies change, not every time you edit server.js. COPY . . then copies the rest of the application code. EXPOSE 3000 documents that the container listens on port 3000. USER node switches from the default root user to a non-privileged user for security. CMD defines the default command.

Building and Running

Build the image from the directory containing your Dockerfile:

1
docker build -t my-express-app .

The -t flag tags the image with a name. The . tells Docker to use the current directory as the build context. Docker will execute each instruction in order, and you will see output for each step.

Run the container:

1
docker run -p 3000:3000 my-express-app

The -p 3000:3000 flag maps port 3000 on your host to port 3000 in the container. Open http://localhost:3000 in your browser and you should see the JSON response.

Useful Commands

Here are the commands you will use most frequently:

  • docker ps — list running containers
  • docker ps -a — list all containers, including stopped ones
  • docker images — list all local images
  • docker stop <container-id> — stop a running container
  • docker rm <container-id> — remove a stopped container
  • docker rmi <image-id> — remove an image
  • docker logs <container-id> — view container logs
  • docker exec -it <container-id> sh — open a shell inside a running container
  • docker system prune — clean up unused containers, images, and networks

Volumes and Persistent Data

By default, data inside a container is ephemeral. When the container is removed, the data is gone. For data that needs to persist, use volumes:

1
docker run -v /host/path:/container/path my-app

This mounts a directory from your host machine into the container. Any files written to /container/path inside the container are actually stored on the host and survive container restarts and removals. For databases in development, this is essential.

Environment Variables

Pass configuration to containers through environment variables rather than hardcoding values:

1
docker run -e NODE_ENV=production -e DB_HOST=localhost my-app

This keeps your images generic and configurable for different environments. The twelve-factor app methodology recommends this approach for good reason.

Multi-Container Applications with Compose

Here is a realistic example using Docker Compose to run an application with a web server and a database:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3.8'
services:
web:
build: .
ports:
- '3000:3000'
environment:
- DB_HOST=db
- DB_PORT=5432
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=appdb
volumes:
- pgdata:/var/lib/postgresql/data

volumes:
pgdata:

Save this as docker-compose.yml and run docker compose up. Both services start together, networked automatically. The depends_on directive ensures the database starts before the web server. The named volume pgdata persists the database across restarts.

Best Practices

Use specific base image tags. node:18-alpine is better than node:latest. The latest tag changes without warning and can break your build unexpectedly.

Minimize layers. Chain related RUN commands: RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*. This keeps the image smaller and reduces the number of layers.

Use .dockerignore. Create a .dockerignore file to exclude node_modules, .git, and other unnecessary files from the build context. This speeds up builds and prevents accidentally copying secrets into the image.

Run as non-root. Always switch to a non-privileged user with the USER instruction. Running as root inside a container can be a security risk if the container is compromised.

Scan for vulnerabilities. Docker Desktop includes docker scan, which checks your images against known vulnerability databases. Run it before deploying to production.

Learning Path

Master Docker incrementally. Start with building and running single containers. Move on to Docker Compose for multi-service applications. Then explore multi-stage builds, which let you use one image for building and a smaller one for running. Finally, look into container orchestration with Docker Swarm or Kubernetes.

Docker’s documentation is excellent, and the community is vast. Almost any problem you encounter has already been solved and documented. Start with the basics, practice regularly, and the rest follows naturally.


Getting Started with Docker – A Complete Beginner's Guide
https://toongs.org/2025/12/25/01-docker-guide/
Author
Jain Chen
Posted on
December 25, 2025
Licensed under