DEV Community

Cover image for How to Write a Dockerfile: Step-by-Step Guide
The Devops Tooling
The Devops Tooling

Posted on • Originally published at thedevopstooling.com

How to Write a Dockerfile: Step-by-Step Guide

Containerization has revolutionized how we build, ship, and deploy applications. At the heart of this transformation lies Docker, and learning how to write a Dockerfile is your gateway to mastering container technology.

Whether you're a developer just starting your DevOps journey or looking to solidify your containerization skills, this guide will walk you through the fundamentals of creating efficient, secure Dockerfiles.

🐳 How Docker Works (Visual)

Understanding the Docker workflow is essential before diving into Dockerfiles:

Dockerfile β†’ Docker Build β†’ Docker Image β†’ Docker Run β†’ Container

How Docker Works

This simple flow shows how a text file (Dockerfile) transforms into a running container. Each step plays a crucial role in containerization.

What is a Dockerfile?

A Dockerfile is a plain text file containing a series of instructions that Docker uses to automatically build images. Think of it as a recipe that tells Docker exactly how to create your application environment, what files to include, and how to configure everything needed to run your application.

Unlike traditional deployment methods where you manually set up servers, a Dockerfile lets you define your entire application environment as code. This means you can version control your infrastructure, share it with your team, and recreate identical environments anywhere Docker runs.

🎯 Why Dockerfiles Are Essential in DevOps

Understanding how to create a Dockerfile is crucial for modern DevOps practices:

Consistency Across Environments: Dockerfiles eliminate the "it works on my machine" problem by ensuring your application runs identically in development, testing, and production environments.

Version Control: Since Dockerfiles are plain text, you can track changes to your application environment alongside your code, making rollbacks and collaboration seamless.

Automation: Dockerfiles enable automated builds and deployments, reducing manual errors and accelerating your delivery pipeline.

Scalability: Container orchestration platforms like Kubernetes rely on Docker images built from Dockerfiles to scale applications efficiently.

Resource Efficiency: Containers created from well-optimized Dockerfiles use fewer resources than traditional virtual machines while providing similar isolation benefits.

πŸ“¦ Basic Structure of a Dockerfile

Before diving into our step-by-step example, let's understand the fundamental building blocks. Every Dockerfile follows a similar pattern using specific instructions:

  • FROM: Specifies the base image to start from
  • WORKDIR: Sets the working directory inside the container
  • COPY: Copies files from your local machine to the container
  • RUN: Executes commands during the build process
  • EXPOSE: Documents which ports the container will use
  • CMD: Defines the default command to run when the container starts

These instructions form the backbone of any Dockerfile, and mastering them is essential for creating effective container images.

πŸš€ How to Write a Dockerfile Step by Step

Let's walk through creating a complete Dockerfile using a practical Python web application example.

Step 1: Set Up Your Project Structure

First, create a new directory for your project:

mkdir my-python-app
cd my-python-app
Enter fullscreen mode Exit fullscreen mode

Create a simple Python application file called app.py:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return '<h1>Hello from Docker!</h1><p>Your Dockerfile is working perfectly!</p>'

@app.route('/health')
def health_check():
    return {'status': 'healthy', 'message': 'Application is running'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
Enter fullscreen mode Exit fullscreen mode

Create a requirements.txt file to specify our Python dependencies:

Flask==2.3.3
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a .dockerignore File

Before writing your Dockerfile, create a .dockerignore file to exclude unnecessary files from your build context. This improves build performance and prevents sensitive files from accidentally being included:

__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv/
pip-log.txt
.git/
.mypy_cache/
.pytest_cache/
*.log
.DS_Store
README.md
Enter fullscreen mode Exit fullscreen mode

Step 3: Write Your First Dockerfile

Now, create a file named Dockerfile (no extension) in your project directory:

# Use Python 3.9 slim image as base
FROM python:3.9-slim

# Create non-root user for security
RUN adduser --disabled-password --gecos '' appuser

# Set working directory in container
WORKDIR /app

# Copy requirements file first (for better caching)
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY app.py .

# Change ownership of app directory to appuser
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Expose port 5000
EXPOSE 5000

# Set environment variable
ENV FLASK_APP=app.py

# Define the command to run the application
CMD ["python", "app.py"]
Enter fullscreen mode Exit fullscreen mode

πŸ” Breaking Down Each Instruction

Let me explain what each line does:

FROM python:3.9-slim: Uses the official Python 3.9 slim image as our starting point. The slim variant is smaller than the full Python image while including everything we need.

RUN adduser: Creates a non-root user called 'appuser' for security purposes. Running containers as root poses unnecessary security risks.

WORKDIR /app: Creates and sets /app as our working directory inside the container. All subsequent commands will run from this directory.

COPY requirements.txt .: Copies our requirements file first to take advantage of Docker's layer caching. If dependencies don't change, Docker reuses this cached layer.

RUN pip install: Installs our Python dependencies. The --no-cache-dir flag prevents pip from storing cache files, keeping our image smaller.

COPY app.py .: Copies our application code to the container.

USER appuser: Switches to the non-root user for all subsequent operations, improving security.

EXPOSE 5000: Documents that our application will listen on port 5000. This doesn't actually open the port but serves as documentation.

ENV FLASK_APP=app.py: Sets an environment variable that Flask uses to locate our application.

CMD ["python", "app.py"]: Specifies the command to run when the container starts. This uses the exec form, which is recommended.


πŸ”₯ Want to Master Dockerfiles?

This is just the beginning! The full guide covers:

βœ… Building and running your Docker image

βœ… Common Dockerfile instructions reference

βœ… Debugging mistakes and troubleshooting

βœ… Security best practices for production

βœ… Multi-stage builds and optimization techniques

βœ… Health checks and monitoring

βœ… Real-world examples and use cases

πŸ‘‰ Read the complete step-by-step guide with advanced techniques, security best practices, and production-ready examples β†’


πŸ’¬ What's your biggest challenge when writing Dockerfiles? Or what's your favorite Docker optimization trick? Share in the comments below!

Top comments (0)