How to Build Docker Image

How to Build Docker Image Docker has revolutionized the way software is developed, tested, and deployed. 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 a foundational skil

Oct 30, 2025 - 12:06
Oct 30, 2025 - 12:06
 1

How to Build Docker Image

Docker has revolutionized the way software is developed, tested, and deployed. 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 a foundational skill for developers, DevOps engineers, and system administrators working in modern cloud-native environments. Whether youre containerizing a simple Python script or a complex microservice architecture, understanding how to build Docker images correctly ensures consistency across development, staging, and production environments.

This guide provides a comprehensive, step-by-step walkthrough on how to build Docker images from scratch. Youll learn not only the mechanics of the process but also the best practices that ensure your images are secure, efficient, and production-ready. Well explore real-world examples, recommend essential tools, and answer frequently asked questions to solidify your understanding. By the end of this tutorial, youll be equipped to build, optimize, and maintain Docker images with confidence.

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)
  • Basic familiarity with the command line
  • A project or application you wish to containerize

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

docker --version

If Docker is properly installed, youll see output similar to:

Docker version 24.0.7, build afdd53b

Next, ensure the Docker daemon is active:

docker info

If you encounter permission errors on Linux, you may need to add your user to the docker group:

sudo usermod -aG docker $USER

Log out and back in for the changes to take effect.

Step 1: Prepare Your Application

Before creating a Docker image, you need a working application. For this guide, well use a simple Python Flask web application as an example. Create a new directory for your project:

mkdir my-flask-app

cd my-flask-app

Inside this directory, create a file named app.py:

from flask import Flask

app = Flask(__name__)

@app.route('/')

def hello():

return "Hello, Docker World!"

if __name__ == '__main__':

app.run(host='0.0.0.0', port=5000)

Next, create a requirements.txt file to list your Python dependencies:

Flask==3.0.0

These files form the foundation of your containerized application. The app.py file contains your application logic, and requirements.txt defines the packages needed to run it.

Step 2: Create a Dockerfile

The Dockerfile is a text file that contains a series of instructions used to build a Docker image. Its the blueprint for your container. Create a file named Dockerfile (with no extension) in the root of your project directory:

touch Dockerfile

Open the Dockerfile in your editor and add the following content:

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 python:3.11-slim Specifies the base image. Using a slim variant reduces image size by excluding unnecessary packages.
  • WORKDIR /app Sets the working directory inside the container. All subsequent commands run relative to this path.
  • COPY . /app Copies all files from the current host directory into the containers /app directory.
  • RUN pip install --no-cache-dir -r requirements.txt Installs Python dependencies. The --no-cache-dir flag prevents pip from storing cached files, reducing image size.
  • EXPOSE 5000 Informs Docker that the container will listen on port 5000 at runtime. This doesnt publish the port thats done at runtime with the -p flag.
  • ENV FLASK_APP=app.py Sets an environment variable used by Flask to locate the application module.
  • CMD ["flask", "run", "--host=0.0.0.0"] Defines the default command to run when the container starts. Use JSON array syntax for better compatibility.

Step 3: Build the Docker Image

With your Dockerfile ready, you can now build the image. From the project root directory (where Dockerfile is located), run:

docker build -t my-flask-app .

The -t flag tags the image with a name (my-flask-app), and the . at the end specifies the build context the current directory. Docker reads the Dockerfile in this directory and executes the instructions sequentially.

As the build progresses, youll see output like:

Sending build context to Docker daemon  4.096kB

Step 1/7 : FROM python:3.11-slim

---> 9a4e4b2c1d7e

Step 2/7 : WORKDIR /app

---> Using cache

---> 5f9a3b1c2d8e

Step 3/7 : COPY . /app

---> 3e7f1d4a5b6c

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

---> Running in 4b8f9a3c1d2e

Collecting Flask==3.0.0

Downloading Flask-3.0.0-py3-none-any.whl (96 kB)

Installing collected packages: Flask

Successfully installed Flask-3.0.0

Removing intermediate container 4b8f9a3c1d2e

---> 7a1b2c3d4e5f

Step 5/7 : EXPOSE 5000

---> Running in 6d7e8f9a0b1c

---> 8f9a0b1c2d3e

Step 6/7 : ENV FLASK_APP=app.py

---> Running in 9c8d7e6f5a4b

---> 6e5f4d3c2b1a

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

---> Running in 5d4c3b2a1f0e

---> 1a2b3c4d5e6f

Successfully built 1a2b3c4d5e6f

Successfully tagged my-flask-app:latest

The final line confirms your image has been built and tagged. You can verify this by listing all local images:

docker images

You should see an entry like:

REPOSITORY        TAG       IMAGE ID       CREATED         SIZE

my-flask-app latest 1a2b3c4d5e6f 2 minutes ago 128MB

Step 4: Run the Container

Now that youve built the image, you can launch a container from it. Use the docker run command:

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

The -p 5000:5000 flag maps port 5000 on your host machine to port 5000 in the container. This allows you to access the application via your browser at http://localhost:5000.

You should see Flasks development server output in your terminal:

 * Running on http://0.0.0.0:5000

Open your browser and navigate to http://localhost:5000. Youll see the message: Hello, Docker World!

Step 5: Stop and Clean Up

To stop the running container, press Ctrl + C in the terminal. To remove the container after stopping it, use:

docker ps -a

This lists all containers, including stopped ones. Note the container ID or name, then remove it:

docker rm 

To remove the image entirely (if needed), use:

docker rmi my-flask-app

Always clean up unused containers and images to free up disk space.

Best Practices

Building Docker images is not just about making them work its about making them efficient, secure, and maintainable. Following industry best practices ensures your containers are production-ready and scalable.

Use Specific Base Image Tags

Always avoid using the latest tag in your FROM instruction. For example:

? Avoid this

FROM python:latest

? Use this instead

FROM python:3.11-slim

Using latest introduces unpredictability a new version of Python may break your application. Pinning to a specific version ensures reproducibility and stability across environments.

Minimize Image Size

Smaller images are faster to build, pull, and deploy. Heres how to reduce size:

  • Use slim or alpine variants of base images (e.g., python:3.11-slim or python:3.11-alpine).
  • Avoid installing unnecessary packages or tools inside the container.
  • Use --no-cache-dir with pip and clean package caches after installation.
  • Merge multiple RUN commands using && to reduce layers.

Example of optimized RUN command:

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

curl \

git \

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

Use .dockerignore

Just as you use .gitignore to exclude files from version control, use .dockerignore to exclude files from the build context. This improves build speed and reduces image size.

Create a .dockerignore file in your project root:

.git

node_modules

__pycache__

*.log

.env

Dockerfile

.dockerignore

These files are ignored during the build process, preventing accidental inclusion of sensitive or unnecessary data.

Multi-Stage Builds for Production

Multi-stage builds allow you to use multiple FROM statements in a single Dockerfile. Each stage can have its own base image and instructions. You can copy only the necessary artifacts from one stage to another, discarding build-time dependencies.

Example for a Node.js application:

Stage 1: Build

FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

RUN npm run build

Stage 2: Production

FROM node:18-alpine

WORKDIR /app

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

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

COPY package*.json ./

EXPOSE 3000

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

This results in a final image that contains only the runtime and built code no development tools, source files, or npm caches.

Use Non-Root Users

Running containers as root is a security risk. Always create and use a non-root user inside your container:

FROM python:3.11-slim

RUN addgroup -g 1001 -S appuser && adduser -u 1001 -S appuser -g appuser

USER appuser

WORKDIR /app

COPY --chown=appuser:appuser . /app

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

EXPOSE 5000

ENV FLASK_APP=app.py

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

The USER instruction switches to the non-root user for all subsequent commands. The --chown flag ensures copied files have the correct ownership.

Label Your Images

Add metadata to your images using the LABEL instruction:

LABEL maintainer="dev-team@example.com"

LABEL version="1.0.0"

LABEL description="Flask web application for user authentication"

These labels help with documentation, auditing, and automation. You can view them later using:

docker inspect my-flask-app

Scan for Vulnerabilities

Regularly scan your images for known security vulnerabilities. Docker provides built-in scanning via docker scan (requires Docker Desktop and a Docker Hub account):

docker scan my-flask-app

Alternatively, use tools like Trivy, Snyk, or ClamAV for advanced scanning and CI/CD integration.

Dont Store Secrets in Images

Never hardcode API keys, passwords, or certificates in your Dockerfile or image. Use environment variables and Docker secrets or external secret managers (e.g., HashiCorp Vault, AWS Secrets Manager) at runtime.

Tools and Resources

Building Docker images becomes more efficient with the right tools. Below are essential utilities and platforms to enhance your workflow.

Docker Desktop

Docker Desktop is the most user-friendly way to run Docker on Windows and macOS. It includes:

  • Docker Engine
  • Docker CLI
  • Docker Compose
  • Kubernetes integration
  • Resource usage monitoring

Download it at https://www.docker.com/products/docker-desktop.

Docker Compose

When your application consists of multiple services (e.g., web server, database, cache), use Docker Compose to define and run multi-container applications. Create a docker-compose.yml file:

version: '3.8'

services:

web:

build: .

ports:

- "5000:5000"

environment:

- FLASK_ENV=development

redis:

image: redis:7-alpine

ports:

- "6379:6379"

Run with:

docker-compose up

BuildKit

BuildKit is Dockers next-generation build backend, offering faster builds, better caching, and improved security. Enable it by setting:

export DOCKER_BUILDKIT=1

Add it to your shell profile (.bashrc, .zshrc) to make it permanent.

Container Registry Services

Once youve built your image, youll need to store and share it. Popular registries include:

  • Docker Hub Free public registry with private repositories available.
  • GitHub Container Registry (GHCR) Integrated with GitHub Actions and repositories.
  • Amazon ECR AWSs managed container registry.
  • Google Container Registry (GCR) Google Clouds container registry.
  • Azure Container Registry (ACR) Microsofts container registry service.

To push your image to Docker Hub:

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

docker login

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

CI/CD Integration Tools

Automate image building and deployment with:

  • GitHub Actions Automate builds on git push.
  • GitLab CI/CD Built-in container registry and pipeline support.
  • Jenkins Extensible automation server with Docker plugins.
  • CircleCI Cloud-based CI/CD with Docker support.

Example GitHub Actions workflow to build and push on tag:

name: Build and Push Docker Image

on:

push:

tags:

- 'v*'

jobs:

build-and-push:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Login to Docker Hub

uses: docker/login-action@v3

with:

username: ${{ secrets.DOCKER_USERNAME }}

password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push

uses: docker/build-push-action@v5

with:

context: .

file: ./Dockerfile

tags: your-dockerhub-username/my-flask-app:${{ github.ref_name }}

Image Analysis and Optimization Tools

  • Trivy Open-source vulnerability scanner for containers.
  • Dive Tool to explore each layer in a Docker image and discover space optimization opportunities.
  • Container Structure Test Validate image structure and content programmatically.

Install Trivy:

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

Scan your image:

trivy image my-flask-app

Real Examples

Lets walk through three real-world scenarios to demonstrate how Docker images are built for different technologies.

Example 1: Node.js Express Application

Project structure:

my-node-app/

??? app.js

??? package.json

??? .dockerignore

??? Dockerfile

app.js:

const express = require('express');

const app = express();

const port = 3000;

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

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

});

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

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

});

package.json:

{

"name": "my-node-app",

"version": "1.0.0",

"main": "app.js",

"scripts": {

"start": "node app.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"]

Build and run:

docker build -t my-node-app .

docker run -p 3000:3000 my-node-app

Example 2: Go Binary Application

Go applications compile into single binaries, making them ideal for minimal Docker images.

main.go:

package main

import (

"fmt"

"net/http"

)

func handler(w http.ResponseWriter, r *http.Request) {

fmt.Fprintf(w, "Hello from Go!")

}

func main() {

http.HandleFunc("/", handler)

http.ListenAndServe(":8080", nil)

}

Dockerfile (multi-stage):

Build stage

FROM golang:1.21-alpine AS builder

WORKDIR /app

COPY . .

RUN go build -o main .

Final stage

FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /app/main .

EXPOSE 8080

CMD ["./main"]

Build and run:

docker build -t my-go-app .

docker run -p 8080:8080 my-go-app

Example 3: React Frontend with Nginx

React apps are static and served via Nginx. Build the app first, then serve it in a lightweight container.

Dockerfile:

Build stage

FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

Production stage

FROM nginx:alpine

COPY --from=builder /app/build /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Build and run:

docker build -t my-react-app .

docker run -p 80:80 my-react-app

Visit http://localhost to see your React app served by Nginx.

FAQs

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

A Docker image is a static, read-only template that contains the application code and dependencies. A container is a runnable instance of an image. You can create, start, stop, move, or delete containers, but images remain unchanged unless rebuilt.

Can I build Docker images on Windows and Linux?

Yes. Docker Desktop supports Windows and macOS, while Docker Engine runs natively on Linux. The Dockerfile syntax is identical across platforms. However, Linux containers on Windows require WSL2 (Windows Subsystem for Linux 2) for full compatibility.

Why is my Docker image so large?

Large images are typically caused by:

  • Using non-slim base images (e.g., python:3.11 instead of python:3.11-slim)
  • Installing unnecessary packages
  • Not cleaning caches after installation
  • Copying large files or directories into the image

Use multi-stage builds and .dockerignore to reduce size.

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

After modifying your application code:

  1. Rebuild the image: docker build -t my-app:latest .
  2. Stop and remove the running container: docker stop my-container && docker rm my-container
  3. Run a new container from the updated image: docker run -p 5000:5000 my-app:latest

For development, consider using volume mounts to sync code changes without rebuilding.

How do I version my Docker images?

Use semantic versioning in your tags: my-app:v1.2.3. Avoid using latest in production. Always tag your images with version numbers and push them to a registry for traceability.

Can I build Docker images without Docker installed?

Yes. You can use cloud-based builders like:

  • GitHub Codespaces
  • GitLab CI/CD
  • Google Cloud Build
  • Buildpacks Tools like Paketo or CNB that build images without a Dockerfile

These platforms provide Docker-like environments in the cloud.

Is it safe to run Docker as root?

No. Running containers as root grants them full access to the host system. Always use non-root users inside containers and avoid running the Docker daemon as root unless absolutely necessary. Use user namespaces and security policies (e.g., SELinux, AppArmor) for added protection.

How do I inspect the layers of a Docker image?

Use the dive tool:

dive my-flask-app

It provides an interactive view of each layer, showing file additions, modifications, and deletions. This helps identify bloat and optimize your Dockerfile.

Conclusion

Building Docker images is a critical skill in modern software development. By following the step-by-step process outlined in this guide from preparing your application and writing a Dockerfile to building, running, and optimizing your container youve gained the foundational knowledge to containerize any application. But mastery comes with practice and adherence to best practices: use minimal base images, avoid secrets in images, leverage multi-stage builds, and scan for vulnerabilities.

The examples provided Python, Node.js, Go, and React demonstrate how Docker adapts to different technologies and deployment models. Whether youre deploying a simple script or a complex microservice, Docker ensures consistency, portability, and scalability.

As you continue your journey, integrate Docker into your CI/CD pipelines, automate image builds, and explore orchestration tools like Kubernetes. The future of software delivery is containerized and now, youre equipped to lead it.