DEV Community

Cover image for Dockerfile A to Z: A Beginner’s Guide to Writing Dockerfiles the Easy Way
Ritesh Singh
Ritesh Singh

Posted on

Dockerfile A to Z: A Beginner’s Guide to Writing Dockerfiles the Easy Way

🐳 What is a Dockerfile?

A Dockerfile is a 📜 plain text file with instructions to build a Docker image — think of it as a recipe for your app’s environment. When Docker reads it, it executes each step to create a lightweight, portable image for your application. 🚀


📘 Dockerfile Structure: Detailed Breakdown

Here’s how a Dockerfile works, broken down into its key components with examples and best practices. 🌟

1. FROM — Base Image 🏗️

FROM node:18
Enter fullscreen mode Exit fullscreen mode
  • Purpose: Defines the starting point by pulling a base image from Docker Hub (e.g., node, python, ubuntu).
  • Why use it? Saves time by inheriting an existing environment (e.g., Node.js pre-installed).
  • ✅ Best Practice: Always specify a version (e.g., node:18 instead of node:latest) to avoid surprises from updates.

2. WORKDIR — Working Directory 📂

WORKDIR /app
Enter fullscreen mode Exit fullscreen mode
  • Purpose: Sets the directory for all subsequent commands. Creates /app if it doesn’t exist.
  • Why use it? Keeps your code organized and simplifies file paths (no need to write /app/server.js repeatedly).
  • ✅ Best Practice: Use one WORKDIR per image unless you need to switch contexts.

3. COPY — Copy Files into the Image 📦

COPY . .
Enter fullscreen mode Exit fullscreen mode
  • Format: COPY <source> <destination>
    • Copies files from your local folder to the container’s /app directory.
  • Example:
  COPY package.json /app/
Enter fullscreen mode Exit fullscreen mode
  • Why use it? Adds your app’s code or dependencies to the image.
  • ✅ Best Practice: Use a .dockerignore file to exclude unnecessary files like node_modules or .git.

4. RUN — Execute Commands During Build ⚙️

RUN npm install
Enter fullscreen mode Exit fullscreen mode
  • Purpose: Runs commands during the image build process. Results are cached for efficiency.
  • Why use it? Installs dependencies or sets up your app before it runs.
  • ✅ Tip: Combine commands for caching (e.g., RUN apt update && apt install -y python), but keep them readable.

5. EXPOSE — Document the Port 🌐

EXPOSE 3000
Enter fullscreen mode Exit fullscreen mode
  • Purpose: Informs Docker (and users) which port the app uses inside the container.
  • Important: This doesn’t publish the port. Use docker run -p to map ports.
  • Example:
  docker run -p 8080:3000 my-image
Enter fullscreen mode Exit fullscreen mode

Maps container’s port 3000 to host’s port 8080.

  • ✅ Best Practice: Always document the port your app uses.

6. CMD — Default Command to Run ▶️

  • Shell Format:
  CMD npm start
Enter fullscreen mode Exit fullscreen mode
  • Runs via a shell. Simple but less efficient.
    • Exec Format (recommended):
  CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode
  • More precise, avoids extra shell processes.
    • Why use it? Specifies the command to launch your app when the container starts.
    • ✅ Best Practice: Use exec format and ensure only one CMD per Dockerfile (last one wins).

🛠️ Sample Dockerfile: Node.js App Example

Here’s a complete Dockerfile for a Node.js app, combining all the pieces. 🚀

# Step 1: Base image
FROM node:18

# Step 2: Set working directory
WORKDIR /app

# Step 3: Copy dependency files first
COPY package*.json ./

# Step 4: Install dependencies
RUN npm install

# Step 5: Copy the rest of the app
COPY . .

# Step 6: Expose port
EXPOSE 3000

# Step 7: Default command
CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

⚡ Dockerfile Mastery Tips

  • Layers 🥞: Each instruction creates a cached layer. Order matters for efficient builds.
  • .dockerignore 🚫: Exclude unneeded files (node_modules, logs, .git) to keep images lean.
  • Order Matters 🔄: Place less-changing instructions (e.g., COPY package.json) early to maximize caching.
  • Security 🔒: Never hardcode passwords or secrets in Dockerfiles. Use environment variables or secrets management.

🌈 Happy Dockerizing! Build lightweight, reproducible images and ship your apps with confidence! 🚢

🛠️ General Best Practice: Three-Step Build Strategy

This industry-standard approach applies to nearly any language project, optimizing Docker builds for speed, efficiency, and scalability. 🚀


📋 Three-Step Build Strategy

1. Copy Dependency Files First 📦

Dependency files change less often, so copying them first maximizes Docker’s layer caching.

# Node.js
COPY package*.json ./

# Python
COPY requirements.txt ./

# Java (Maven)
COPY pom.xml ./

# Go
COPY go.mod go.sum ./
Enter fullscreen mode Exit fullscreen mode
  • Why? Ensures dependency installation only runs when these files change.

2. Install Dependencies ⚙️

This step is time-consuming, so isolating it prevents unnecessary re-runs.

# Node.js
RUN npm install

# Python
RUN pip install -r requirements.txt

# Java (Maven)
RUN mvn dependency:resolve

# Go
RUN go mod download
Enter fullscreen mode Exit fullscreen mode
  • Why? Caches dependencies separately, speeding up builds when only code changes.

3. Copy the Rest of the Application 📂

Add source files and configurations last, as they change most frequently.

COPY . .
Enter fullscreen mode Exit fullscreen mode
  • Why? Isolates frequent code changes to a single layer, avoiding redundant dependency installs.

📦 Minimal Dockerfile Examples by Language

Node.js

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
Enter fullscreen mode Exit fullscreen mode

Python

FROM python:3.11
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Enter fullscreen mode Exit fullscreen mode

Java (Maven)

FROM maven:3.9.4-eclipse-temurin-17
WORKDIR /app
COPY pom.xml ./
RUN mvn dependency:resolve
COPY . .
RUN mvn package
CMD ["java", "-jar", "target/app.jar"]
Enter fullscreen mode Exit fullscreen mode

Go (Golang)

FROM golang:1.21
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
Enter fullscreen mode Exit fullscreen mode

🎯 Why This Pattern Works

Step Benefits
Copy dependencies first Rarely changes, maximizes Docker layer cache for faster builds.
Install dependencies Avoids re-running on every rebuild, saving time and resources.
Copy app code last Isolates frequent code changes, preventing unnecessary dependency re-installs.

🧠 Summary

The three-step build strategy is the best approach because it:

  • 🧱 Leverages Docker’s caching for faster, efficient builds.
  • Speeds up CI/CD pipelines (Jenkins, GitHub Actions, GitLab CI).
  • 🧼 Keeps builds clean by separating dependencies from code.
  • 📦 Works across all languages (Node.js, Python, Java, Go, etc.).
  • ☁️ Scales well for production-grade pipelines.

✅ Why This Is the Best Approach

Feature Why It Matters
🧱 Layered Build Docker caches each instruction, so stable layers (e.g., package.json) save time.
Faster Rebuilds Only rebuild changed layers, critical for rapid development and CI/CD.
🧼 Clean Separation Dependencies and app logic are isolated, simplifying debugging and testing.
📦 Language-Agnostic Works for Node.js, Python, Java, Go, and more — just swap the tools.
☁️ CI/CD Friendly Optimized for tools like Jenkins, GitHub Actions, and GitLab CI that rely on caching.

🌈 Build smarter, ship faster! 🚢

THE BEST TABLE

Language Base Image Dependency File(s) Run Command CMD to Start App
Node.js node:18 package*.json npm install ["node", "app.js"]
Python python:3.11 requirements.txt pip install -r requirements.txt ["python", "app.py"]
Java (Maven) maven:3.9.4-eclipse-temurin-17 pom.xml mvn dependency:resolve ["java", "-jar", "target/app.jar"]
Go (Golang) golang:1.21 go.mod, go.sum go mod download ["./myapp"]
Ruby ruby:3.2 Gemfile, Gemfile.lock bundle install ["rails", "server", "-b", "0.0.0.0"]
PHP php:8.2-fpm composer.json, composer.lock composer install ["php-fpm"]
C# (.NET) mcr.microsoft.com/dotnet/sdk:8.0 *.csproj dotnet restore ["dotnet", "run", "--project", "app.csproj"]

c# (.net)

FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet build
CMD ["dotnet", "run", "--project", "app.csproj"]
Enter fullscreen mode Exit fullscreen mode

PHP

FROM php:8.2-fpm
WORKDIR /var/www
COPY composer.json composer.lock ./
RUN composer install
COPY . .
EXPOSE 9000
CMD ["php-fpm"]
Enter fullscreen mode Exit fullscreen mode

👨‍💻 Author

Ritesh Singh

🌐 LinkedIn | 📝 Hashnode | GITHUB

Top comments (0)