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 • Edited 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)