Skip to content
Home » Docker for Beginners: From Zero to Your First Container in 15 Minutes

Docker for Beginners: From Zero to Your First Container in 15 Minutes

What Is Docker and Why Should You Care?

Docker packages your application and all its dependencies into a single unit called a container. Unlike virtual machines, containers share the host operating system’s kernel, which makes them start in seconds and use a fraction of the memory.

That is the two-sentence version. Here is why it matters practically: you build something on your laptop, it works. You deploy it to a server, it breaks. Different Node version, missing library, wrong OS config. Docker eliminates that entire category of problems. The container runs identically everywhere — your machine, your colleague’s machine, staging, production.

According to the 2025 Stack Overflow Developer Survey, 73.8% of professional developers used Docker in the past year, ahead of Kubernetes (30.1%) and Terraform (18.7%). Docker gained roughly 17 percentage points on the previous year — the largest single-year jump of any tool in that survey. If you write code that runs on servers, Docker is no longer optional knowledge.

This guide assumes you have never touched Docker before. By the end, you will have a running Node.js application inside a container. No theory overload. Just the commands you need, explained as we go.

How Do You Install Docker on Your Machine?

Installation takes under five minutes on any operating system. Docker Desktop handles everything on Mac and Windows. Linux users install Docker Engine directly.

Linux (Ubuntu/Debian):

# Remove old versions if present
sudo apt-get remove docker docker-engine docker.io containerd runc

# Install using the official convenience script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add your user to the docker group (avoids needing sudo)
sudo usermod -aG docker $USER

# Log out and back in, then verify
docker --version

Mac: Download Docker Desktop from docker.com/products/docker-desktop. Open the .dmg file, drag Docker to Applications, launch it. Wait for the whale icon in the menu bar. Done.

Windows: Download Docker Desktop from the same URL. Run the installer. It will enable WSL 2 (Windows Subsystem for Linux) automatically if needed. Restart when prompted. After restart, Docker Desktop launches and you are ready.

Verify the installation works:

docker --version
# Docker version 27.5.1, build 2025-xxx

docker run hello-world
# Should pull the hello-world image and print a confirmation message

If docker run hello-world prints “Hello from Docker!” — your installation works. If you get a permission error on Linux, you either forgot to add your user to the docker group or forgot to log out and back in. Do both and try again.

What Are the Core Concepts You Need to Know?

Five concepts cover 90% of daily Docker usage: images, containers, Dockerfiles, volumes, and port mapping. Everything else builds on top of these.

Images are blueprints. An image contains your application code, its runtime (Node, Python, Java — whatever), system libraries, and configuration files. Images are read-only. You build them once, then create containers from them. Think of an image as a class in object-oriented programming.

Containers are running instances of images. One image can spawn ten containers. Each container has its own filesystem, network interface, and process space. Containers are ephemeral by default — when you stop and remove one, its data vanishes. This is intentional.

Dockerfiles are text files that define how to build an image. Each line is an instruction: start from a base image, copy files, run commands, expose ports. Docker reads the Dockerfile top to bottom and creates the image layer by layer.

Volumes solve the ephemeral problem. A volume is a persistent storage location managed by Docker. You mount it into a container, and data written there survives container restarts and removal. Database containers always need volumes. Application containers usually do not.

Port mapping connects the container’s internal network to your host machine. A Node.js app inside a container listens on port 3000. But that port only exists inside the container. To reach it from your browser, you map host port 3000 to container port 3000 using the -p flag. The syntax is -p host:container.

How Do You Build Your First Node.js Container?

Let’s build a real application in Docker. We will create a minimal Express server, write a Dockerfile for it, and run it — all in about 10 minutes.

Start by creating a project directory and the application file:

mkdir my-docker-app && cd my-docker-app

Create package.json:

{
  "name": "my-docker-app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.21.0"
  }
}

Create server.js:

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.json({
    message: 'Hello from Docker!',
    timestamp: new Date().toISOString(),
    hostname: require('os').hostname()
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

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

Now the Dockerfile. Create a file named exactly Dockerfile (no extension) in the project root:

# Use the official Node.js 22 Alpine image (small footprint)
FROM node:22-alpine

# Set the working directory inside the container
WORKDIR /app

# Copy package files first (leverages Docker layer caching)
COPY package.json ./

# Install dependencies
RUN npm install --production

# Copy the rest of the application code
COPY . .

# Tell Docker this container listens on port 3000
EXPOSE 3000

# Define the command that runs when the container starts
CMD ["npm", "start"]

A critical detail about the Dockerfile: we copy package.json and run npm install before copying the rest of the code. This is deliberate. Docker caches each layer. If your code changes but your dependencies do not, Docker reuses the cached npm install layer. Builds go from 30 seconds to 2 seconds. This pattern is standard practice in production Dockerfiles [Docker Official Documentation, “Best practices for writing Dockerfiles,” 2025].

Add a .dockerignore file to keep your image clean:

node_modules
npm-debug.log
.git
.gitignore

Build and run:

# Build the image (the dot means "use the current directory as context")
docker build -t my-docker-app .

# Run the container
docker run -d -p 3000:3000 --name my-app my-docker-app

# Test it
curl http://localhost:3000

You should see a JSON response with the message, timestamp, and a container hostname. That hostname is the container’s ID — proof that the response comes from inside the container, not your host machine.

How Do You Use Docker Compose for Multi-Container Setups?

Docker Compose lets you define and run multi-container applications with a single YAML file. For our project, it simplifies the run command and makes configuration repeatable.

Create docker-compose.yml in the project root:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

Now instead of the long docker run command, you use:

# Start the application
docker compose up -d

# View logs
docker compose logs -f

# Stop everything
docker compose down

Docker Compose becomes essential when your application needs a database, a cache, or any other service running alongside it. Adding a Redis cache, for example, takes four lines in the YAML file. Adding a PostgreSQL database takes six. Each service gets its own container, its own network, and its own lifecycle — all managed through one file.

What Commands Will You Use Every Day?

Here is the cheat sheet. These twelve commands handle 95% of daily Docker work. Print this or bookmark it.

Command What It Does
docker build -t name . Build an image from the Dockerfile in the current directory
docker run -d -p 3000:3000 name Run a container in detached mode with port mapping
docker ps List all running containers
docker ps -a List all containers including stopped ones
docker stop container_id Gracefully stop a running container
docker rm container_id Remove a stopped container
docker logs container_id View container output (stdout and stderr)
docker logs -f container_id Follow logs in real time (like tail -f)
docker exec -it container_id sh Open a shell inside a running container
docker images List all locally stored images
docker rmi image_id Delete a local image
docker system prune Remove all unused containers, networks, and dangling images

Two notes on the table above. First, docker exec -it container_id sh is your debugging lifeline. When something goes wrong inside a container, this drops you into a shell where you can inspect files, check environment variables, and test connectivity. The -it flags mean “interactive” and “allocate a TTY” — without them, the shell opens and immediately closes.

Second, docker system prune is your disk space savior. Docker images and stopped containers accumulate fast. On a busy development machine, you can reclaim 10-20 GB by running this command weekly. Add -a to also remove images not associated with any container — but be aware this forces a full rebuild next time.

What Goes Wrong and How Do You Fix It?

Four problems account for most beginner frustration with Docker. Each one has a straightforward fix once you know what to look for.

Problem: “Port already in use.” You see Bind for 0.0.0.0:3000 failed: port is already allocated. Either another container or a local process is using that port. Run docker ps to check for containers. Run lsof -i :3000 on Mac/Linux or netstat -ano | findstr 3000 on Windows to find local processes. Stop the conflicting process, or change your host port: -p 3001:3000 maps host port 3001 to container port 3000.

Problem: “Container exits immediately.” You run docker run -d my-app and the container stops within a second. Check the logs with docker logs container_id. Nine times out of ten, the application inside crashed. Common causes: missing environment variables, wrong working directory in the Dockerfile, or a typo in the CMD instruction. Fix the root cause, rebuild, and run again.

Problem: “Changes to code are not reflected.” You edited server.js but the container still serves the old version. Containers run a snapshot of your code taken at build time. You need to rebuild: docker build -t my-app . && docker stop my-app && docker rm my-app && docker run -d -p 3000:3000 --name my-app my-app. During development, mount your source code as a volume to see changes without rebuilding: -v $(pwd):/app.

Problem: “Image is too large.” Your image is 900 MB for a simple Node.js app. Use Alpine-based images (node:22-alpine is ~180 MB vs ~1 GB for node:22). Add a proper .dockerignore to exclude node_modules, .git, and test files. Use multi-stage builds for production images — the Docker documentation covers this in detail [Docker Docs, “Multi-stage builds,” 2025].

Where Do You Go After Your First Container?

You have the foundation. Three topics deserve your attention next: multi-stage builds for smaller production images, Docker networking for container-to-container communication, and orchestration with Docker Compose for multi-service applications.

Multi-stage builds let you compile or bundle your application in one stage and copy only the final artifact into a slim production image. A typical Node.js multi-stage build produces images under 100 MB. This matters for deployment speed and security — fewer packages in the image means fewer potential vulnerabilities.

Docker networking is what allows containers to talk to each other. When you run a web server and a database in separate containers, they need to communicate. Docker Compose creates a shared network automatically. Understanding how DNS resolution works between containers — each service is reachable by its name defined in the YAML file — will save you hours of debugging.

For orchestration beyond a single machine, Kubernetes is the industry standard. But do not jump there yet. Master Docker Compose for local development and single-server deployments first. Many production applications run perfectly fine on a single server with Docker Compose, a reverse proxy like Nginx or Traefik, and a CI/CD pipeline that rebuilds and redeploys on git push.

The Docker Official Documentation at docs.docker.com remains the best reference. It is maintained actively, includes runnable examples, and covers edge cases that tutorials skip. Bookmark it. You will come back to it often.

Start with the application you built today. Add a database. Add a reverse proxy. Break things. Read the logs. Fix them. That cycle — build, break, debug, fix — is how Docker knowledge sticks.

Sources:

Which articles go with this?

Leave a Reply

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

Ein Webprojekt von DeOlivera Webprojekte