How to Build Docker Image

How to Build Docker Image Docker has revolutionized the way software is developed, deployed, and scaled. At the heart of Docker’s power lies the Docker image — a lightweight, standalone, executable package that includes everything needed to run a piece of software, including the code, runtime, libraries, environment variables, and configuration files. Building a Docker image is the foundational st

Nov 10, 2025 - 11:34
Nov 10, 2025 - 11:34
 0

How to Build Docker Image

Docker has revolutionized the way software is developed, deployed, and scaled. At the heart of Dockers power lies the Docker image a lightweight, standalone, executable package that includes everything needed to run a piece of software, including the code, runtime, libraries, environment variables, and configuration files. Building a Docker image is the foundational step in containerizing applications, enabling consistent behavior across development, testing, and production environments. Whether you're a developer, DevOps engineer, or system administrator, mastering how to build Docker images is essential for modern software delivery.

This guide provides a comprehensive, step-by-step walkthrough on how to build Docker images from scratch. Well cover everything from writing a Dockerfile to optimizing images for production, along with industry best practices, essential tools, real-world examples, and answers to common questions. By the end of this tutorial, youll have the knowledge and confidence to create efficient, secure, and scalable Docker images tailored to your applications needs.

Step-by-Step Guide

Prerequisites

Before you begin building Docker images, ensure your system meets the following requirements:

  • Docker Engine installed on your machine (Windows, macOS, or Linux)
  • A text editor or IDE (e.g., VS Code, Sublime Text, or Vim)
  • Basic familiarity with the command line
  • A sample application or codebase you wish to containerize

To verify Docker is installed and running, open your terminal and run:

docker --version

You should see output similar to:

Docker version 24.0.7, build afdd53b

If Docker is not installed, visit https://docs.docker.com/get-docker/ to download and install the appropriate version for your operating system.

Step 1: Prepare Your Application

Before creating a Docker image, organize your applications files in a clean directory structure. For example, if youre containerizing a Python web application, your project folder might look like this:

my-app/

??? app.py

??? requirements.txt

??? README.md

??? .dockerignore

The app.py file contains your application code. The requirements.txt file lists Python dependencies. The .dockerignore file (which well create next) helps exclude unnecessary files from the build context.

Step 2: Create a Dockerfile

The Dockerfile is a text file that contains a series of instructions used to build a Docker image. It must be named exactly Dockerfile (no extension) and placed in the root of your project directory.

Open your terminal and navigate to your project folder. Create the Dockerfile:

touch Dockerfile

Now open the file in your editor and add the following content for a Python Flask application:

Use an official Python runtime as a parent image

FROM python:3.11-slim

Set the working directory in the container

WORKDIR /app

Copy the current directory contents into the container at /app

COPY . /app

Install any needed packages specified in requirements.txt

RUN pip install --no-cache-dir -r requirements.txt

Make port 5000 available to the world outside this container

EXPOSE 5000

Define environment variable

ENV FLASK_APP=app.py

Run app.py when the container launches

CMD ["flask", "run", "--host=0.0.0.0"]

Lets break down each instruction:

  • FROM Specifies the base image. We use python:3.11-slim to reduce image size while retaining functionality.
  • WORKDIR Sets the working directory inside the container. All subsequent commands run relative to this path.
  • COPY Copies files from your local machine into the container. Avoid using COPY * to prevent copying unnecessary files.
  • RUN Executes commands during the build process. Here, we install Python dependencies.
  • EXPOSE Informs Docker that the container listens on port 5000 at runtime. This is documentation; it doesnt publish the port.
  • ENV Sets environment variables used by the application.
  • CMD Defines the default command to run when the container starts. Only one CMD instruction is allowed per Dockerfile.

Step 3: Create a .dockerignore File

Just as a .gitignore file excludes files from version control, a .dockerignore file excludes files from the Docker build context. This improves build speed and reduces image size by preventing unnecessary files from being copied into the image.

Create a .dockerignore file in your project root:

touch .dockerignore

Add the following content:

.git

node_modules

__pycache__

*.log

.DS_Store

.env

venv/

This ensures that Git metadata, virtual environments, logs, and OS-specific files are excluded from the build context.

Step 4: Build the Docker Image

With your Dockerfile and .dockerignore in place, youre ready to build the image. In your terminal, navigate to the project directory and run:

docker build -t my-flask-app .

Lets examine the command:

  • docker build Tells Docker to build an image.
  • -t my-flask-app Tags the image with a name (my-flask-app). You can also include a version tag like my-flask-app:1.0.
  • . Specifies the build context (the current directory). Docker looks for the Dockerfile here.

Docker will now execute each instruction in the Dockerfile sequentially. Youll see output similar to:

Sending build context to Docker daemon  3.584kB

Step 1/7 : FROM python:3.11-slim

---> 1a2b3c4d5e6f

Step 2/7 : WORKDIR /app

---> Using cache

---> 2b3c4d5e6f7a

Step 3/7 : COPY . /app

---> 3c4d5e6f7a8b

Step 4/7 : RUN pip install --no-cache-dir -r requirements.txt

---> Running in 9f8e7d6c5b4a

Collecting Flask==2.3.3

Downloading Flask-2.3.3-py3-none-any.whl (101 kB)

Installing collected packages: Flask

Successfully installed Flask-2.3.3

Removing intermediate container 9f8e7d6c5b4a

---> 4d5e6f7a8b9c

Step 5/7 : EXPOSE 5000

---> Running in 5e6f7a8b9c0d

---> 6f7a8b9c0d1e

Step 6/7 : ENV FLASK_APP=app.py

---> Running in 7a8b9c0d1e2f

---> 8b9c0d1e2f3a

Step 7/7 : CMD ["flask", "run", "--host=0.0.0.0"]

---> Running in 9c0d1e2f3a4b

---> a0b1c2d3e4f5

Successfully built a0b1c2d3e4f5

Successfully tagged my-flask-app:latest

Once complete, verify the image was created by listing all local images:

docker images

You should see your image listed:

REPOSITORY        TAG       IMAGE ID       CREATED         SIZE

my-flask-app latest a0b1c2d3e4f5 2 minutes ago 120MB

Step 5: Run the Container

Now that youve built the image, you can run it as a container. Use the following command:

docker run -p 5000:5000 my-flask-app

  • -p 5000:5000 Maps port 5000 on your host machine to port 5000 in the container.
  • my-flask-app The image name to run.

Open your browser and navigate to http://localhost:5000. If your Flask app is correctly configured, you should see your application running.

Step 6: Stop and Clean Up

To stop the running container, press Ctrl + C in the terminal. To list all containers (including stopped ones), run:

docker ps -a

To remove a stopped container, use:

docker rm <container_id>

To remove the image (if you no longer need it), use:

docker rmi my-flask-app

Best Practices

Use Official Base Images

Always prefer official images from Docker Hub (e.g., python, node, nginx, alpine) over unofficial or custom images. Official images are maintained by the software vendors and undergo regular security audits. For example, use python:3.11-slim instead of python:latest to avoid unexpected breaking changes.

Minimize Image Layers

Each instruction in a Dockerfile creates a new layer. Too many layers increase image size and slow down builds. Combine related commands using && and line continuations (\). For example:

RUN apt-get update && apt-get install -y \

curl \

vim \

&& rm -rf /var/lib/apt/lists/*

This reduces the number of layers and removes temporary files in the same step.

Use .dockerignore to Exclude Unnecessary Files

As previously mentioned, the .dockerignore file prevents irrelevant files from being copied into the build context. This not only speeds up builds but also reduces the attack surface by excluding sensitive files like .env or config.json.

Avoid Running as Root

By default, Docker containers run as the root user, which poses a security risk. Create a non-root user inside the container:

FROM python:3.11-slim

RUN addgroup --system appuser && adduser --system --group appuser

WORKDIR /app

COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

USER appuser

EXPOSE 5000

CMD ["flask", "run", "--host=0.0.0.0"]

This ensures that even if an attacker compromises the container, they cannot escalate privileges to root.

Use Multi-Stage Builds for Production Images

Multi-stage builds allow you to use multiple FROM statements in a single Dockerfile. Each stage can have its own base image and instructions. The final stage contains only the artifacts needed for production, drastically reducing image size.

Example for a Node.js application:

Build stage

FROM node:18 AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

RUN npm run build

Production stage

FROM node:18-alpine

WORKDIR /app

COPY --from=builder /app/node_modules ./node_modules

COPY --from=builder /app/dist ./dist

COPY --from=builder /app/package*.json ./

EXPOSE 3000

CMD ["node", "dist/index.js"]

The final image is only ~100MB instead of over 1GB, because it doesnt include build tools, source code, or development dependencies.

Pin Dependency Versions

Never use floating tags like latest in production. Always pin versions in your Dockerfile and package manifests:

  • FROM python:3.11.6-slim instead of FROM python:3.11
  • flask==2.3.3 instead of flask>=2.0

This ensures reproducible builds and prevents unexpected behavior due to dependency updates.

Scan Images for Vulnerabilities

Regularly scan your images for known security vulnerabilities using tools like Docker Scout, Trivy, or Clair. For example, with Trivy:

trivy image my-flask-app

Integrate scanning into your CI/CD pipeline to block deployments of vulnerable images.

Optimize Build Cache

Docker caches each layer to speed up subsequent builds. Order your Dockerfile instructions from least to most frequently changing:

  1. FROM rarely changes
  2. COPY requirements.txt changes infrequently
  3. RUN pip install changes when dependencies change
  4. COPY . changes with every code update

This way, only the final layer needs to be rebuilt when your source code changes.

Tools and Resources

Essential Docker Tools

  • Docker Desktop The official GUI for macOS and Windows, simplifying Docker setup and management.
  • Docker CLI Command-line interface for building, running, and managing containers.
  • Docker Compose Used to define and run multi-container applications using a YAML file. Ideal for local development with databases, caches, and microservices.
  • Docker Buildx An extended build client that supports multi-platform builds (e.g., building ARM images on x86 machines).
  • Docker Scout A security and compliance tool for analyzing images, detecting vulnerabilities, and tracking software bills of materials (SBOMs).
  • Trivy An open-source vulnerability scanner for containers and other artifacts.
  • Hadolint A linter for Dockerfiles that detects common mistakes and enforces best practices.

Image Registries

Once youve built an image, youll need to store it in a registry for sharing and deployment:

  • Docker Hub Free public registry with unlimited public repositories and limited private repos.
  • GitHub Container Registry (GHCR) Integrated with GitHub Actions and repositories. Ideal for open-source projects.
  • Amazon ECR AWS-managed container registry with tight integration for EC2, ECS, and EKS.
  • Google Container Registry (GCR) Google Clouds container registry, optimized for GKE.
  • GitLab Container Registry Built into GitLab CI/CD pipelines.

To push an image to Docker Hub:

docker tag my-flask-app your-dockerhub-username/my-flask-app:1.0

docker login

docker push your-dockerhub-username/my-flask-app:1.0

Online Resources

CI/CD Integration

Automate Docker image builds using CI/CD platforms:

  • GitHub Actions Use docker/build-push-action to build and push images on push or pull request.
  • GitLab CI Leverage built-in Docker-in-Docker (DinD) support.
  • CircleCI Use the Docker executor and orb for streamlined builds.
  • Jenkins Install the Docker Pipeline plugin to integrate Docker commands into pipelines.

Example GitHub Actions workflow for building and pushing a Docker image:

name: Build and Push Docker Image

on:

push:

branches: [ main ]

jobs:

build:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Set up Docker Buildx

uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub

uses: docker/login-action@v3

with:

username: ${{ secrets.DOCKERHUB_USERNAME }}

password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push

uses: docker/build-push-action@v5

with:

context: .

file: ./Dockerfile

push: true

tags: your-dockerhub-username/my-app:latest

Real Examples

Example 1: Node.js Express Application

Lets containerize a simple Express server.

Project structure:

node-app/

??? server.js

??? package.json

??? .dockerignore

??? Dockerfile

server.js:

const express = require('express');

const app = express();

const port = 3000;

app.get('/', (req, res) => {

res.send('Hello from Dockerized Node.js!');

});

app.listen(port, '0.0.0.0', () => {

console.log(Server running at http://0.0.0.0:${port});

});

package.json:

{

"name": "node-app",

"version": "1.0.0",

"main": "server.js",

"scripts": {

"start": "node server.js"

},

"dependencies": {

"express": "^4.18.2"

}

}

Dockerfile:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

.dockerignore:

node_modules

npm-debug.log

.git

Build and run:

docker build -t node-app .

docker run -p 3000:3000 node-app

Example 2: Nginx Static Website

Deploy a static HTML site using Nginx.

Project structure:

static-site/

??? index.html

??? styles.css

??? script.js

??? Dockerfile

index.html:

<!DOCTYPE html>

<html>

<head>

<title>My Static Site</title>

<link rel="stylesheet" href="styles.css">

</head>

<body>

<h1>Welcome to My Dockerized Site</h1>

<script src="script.js"></script>

</body>

</html>

Dockerfile:

FROM nginx:alpine

COPY index.html /usr/share/nginx/html/

COPY styles.css /usr/share/nginx/html/

COPY script.js /usr/share/nginx/html/

EXPOSE 80

Build and run:

docker build -t static-site .

docker run -p 8080:80 static-site

Visit http://localhost:8080 to see your site.

Example 3: Multi-Stage Python + Pandas Data App

Build a lightweight image for a data processing script using pandas.

data-app.py:

import pandas as pd

df = pd.read_csv('data.csv')

print(df.head())

Dockerfile:

Build stage

FROM python:3.11-slim AS builder

WORKDIR /app

COPY requirements.txt .

RUN pip install --user --no-cache-dir -r requirements.txt

Final stage

FROM python:3.11-slim

WORKDIR /app

Copy only the installed packages from builder stage

COPY --from=builder /root/.local /root/.local

COPY data.csv .

COPY data-app.py .

ENV PATH=/root/.local/bin:$PATH

CMD ["python", "data-app.py"]

This approach avoids installing pandas and its heavy dependencies (like NumPy) in the final image, reducing size from ~1.2GB to ~150MB.

FAQs

What is the difference between a Docker image and a container?

A Docker image is a read-only template with instructions for creating a container. A container is a runnable instance of an image. Think of the image as a class in object-oriented programming and the container as an instance of that class.

Can I build Docker images on Windows and Linux?

Yes. Docker Desktop for Windows and macOS includes a Linux VM to run containers. On Linux, Docker runs natively. The build process is identical across platforms.

Why is my Docker image so large?

Large images are often caused by:

  • Using non-slim base images (e.g., python:3.11 instead of python:3.11-slim)
  • Installing development tools or unnecessary packages
  • Not cleaning up cache files (e.g., apt-get clean)
  • Not using multi-stage builds

Use docker history <image> to inspect layer sizes and identify bloat.

How do I update a Docker image after changing the code?

Rebuild the image with the same tag:

docker build -t my-app .

Then stop and remove the old container, and run a new one:

docker stop my-container

docker rm my-container

docker run -p 5000:5000 my-app

Can I build images without a Dockerfile?

No. A Dockerfile is required to define the build steps. However, you can use tools like docker commit to create an image from a running container but this is discouraged for production use as it lacks reproducibility and version control.

How do I share my Docker image with others?

Push it to a registry like Docker Hub or GitHub Container Registry. Others can then pull it using:

docker pull your-username/your-image:tag

Is it safe to use the latest tag in production?

No. The latest tag is mutable and can change without notice. Always use specific version tags (e.g., python:3.11.6) for reproducible, stable deployments.

How do I debug a failing Docker build?

Run the build with verbose output:

docker build --no-cache -t my-app .

The --no-cache flag forces Docker to rebuild every layer, helping you identify where the failure occurs. You can also run an interactive container from a previous successful layer to inspect its state:

docker run -it <image-id> /bin/bash

Conclusion

Building Docker images is a critical skill in modern software development. By following the steps outlined in this guide from writing a clean Dockerfile and using .dockerignore to implementing multi-stage builds and scanning for vulnerabilities you can create lightweight, secure, and reproducible containers that work consistently across environments.

Remember that Docker is not just a tool for deployment its a philosophy of encapsulation, portability, and automation. When done right, Docker images become the foundation of scalable microservices, CI/CD pipelines, and cloud-native applications.

Start small: containerize a simple script or web app. Then gradually adopt best practices pin versions, use non-root users, optimize layers, and automate builds. As you gain experience, youll discover how Docker transforms not just how you deploy code, but how you think about software architecture.

Now that you know how to build Docker images, the next step is to orchestrate them using Docker Compose for local development and Kubernetes for production. But thats a topic for another guide. For now, build, test, and iterate. Your containerized future is waiting.