Skip to content

1.04 Docker Images

Docker images are packaged, reusable templates used to create containers. An image contains the application code, runtime, libraries, dependencies, environment settings, and startup command needed to run an application consistently.

Goal

Learn how Docker images are created using a Dockerfile, how image layers work, and how to build production-ready images.


Why Create Your Own Docker Image?

You create a custom Docker image when:

  • your application is not already available as a ready-made image
  • your team wants to package an application for consistent deployment
  • you need specific dependencies, runtime versions, or configuration
  • you want the same application to run across dev, test, staging, and production

Note

Docker images solve the “works on my machine” problem by packaging the application and its dependencies together.


Manual Deployment vs Docker Image

Before writing a Dockerfile, think about the manual steps needed to run the application.

For a Python Flask app, the manual setup may look like this:

  1. Start with Ubuntu
  2. Update package repositories
  3. Install OS dependencies
  4. Install Python dependencies
  5. Copy application source code
  6. Run the web server

A Dockerfile converts these manual steps into repeatable build instructions.


Dockerfile Basics

A Dockerfile is a text file that contains instructions Docker uses to build an image.

FROM ubuntu

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

RUN pip install flask flask-mysql

COPY . /opt/source-code

ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run

Use exact filename

The default file name should be Dockerfile with no extension.


Dockerfile Instruction Format

Dockerfile instructions follow this pattern:

INSTRUCTION argument

Example:

FROM ubuntu

Here:

  • FROM is the instruction
  • ubuntu is the argument

Common Dockerfile instructions:

Instruction Purpose
FROM Sets the base image
RUN Runs commands during image build
COPY Copies files from host to image
WORKDIR Sets working directory
ENV Sets environment variables
EXPOSE Documents the container port
ENTRYPOINT Defines the main command
CMD Provides default command/arguments

Step-by-Step Dockerfile Explanation

FROM ubuntu

Starts from a base image.

Every Dockerfile must begin with a FROM instruction.

Tip

In production, prefer smaller and more specific base images such as python:3.12-slim instead of a generic ubuntu image.

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

Runs commands while building the image.

This is commonly used to install operating system packages.

RUN pip install flask flask-mysql

Installs Python dependencies.

Warning

In real projects, avoid installing dependencies directly in the Dockerfile one by one. Use a dependency file such as requirements.txt.

COPY . /opt/source-code

Copies application files from the current directory on the host into the image.

ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run

Defines the command that runs when a container starts from the image.

Note

ENTRYPOINT is used when the container should always run a specific executable or command.


Build a Docker Image

Use docker build to create an image from a Dockerfile.

docker build . -f Dockerfile -t mumshad/my-custom-app

Explanation:

Part Meaning
docker build Builds a Docker image
. Build context/current directory
-f Dockerfile Dockerfile path
-t mumshad/my-custom-app Image name/tag

Build with a version tag

docker build . -t myapp:1.0

Production practice

Always tag images with meaningful versions.

docker build . -t registry.example.com/myteam/myapp:1.0.0

Push Image to Registry

After building an image locally, push it to a registry so other systems can pull and run it.

docker push mumshad/my-custom-app

Common registries:

  • Docker Hub
  • Amazon ECR
  • Google Artifact Registry
  • Azure Container Registry
  • GitHub Container Registry
  • Harbor

Authentication required

You must log in before pushing to most registries.

docker login
docker push mumshad/my-custom-app

Image Layers

Docker builds images in layers. Each Dockerfile instruction creates a new layer.

Example:

FROM ubuntu
RUN apt-get update && apt-get -y install python
RUN pip install flask flask-mysql
COPY . /opt/source-code
ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run

Layer breakdown:

Layer Instruction Example Size
1 Base Ubuntu layer 120 MB
2 APT package changes 306 MB
3 Python package changes 6.3 MB
4 Source code small
5 Entrypoint metadata 0 B

Note

A layer stores only the changes from the previous layer.


Why Layers Matter

Docker caches layers during builds.

If a build fails at step 4, Docker can reuse layers from steps 1 to 3 during the next build.

This makes rebuilds faster.

Layer cache benefit

Docker does not rebuild unchanged layers. It resumes from the first changed layer.

Example:

RUN apt-get update && apt-get -y install python
RUN pip install flask flask-mysql
COPY . /opt/source-code

If only source code changes, Docker can reuse dependency layers and rebuild only the COPY layer and later layers.


Check Image Build History

Use docker history to inspect image layers.

docker history mumshad/my-custom-app

This shows:

  • image layers
  • commands used to create layers
  • layer sizes
  • creation order

Tip

Use docker history to find unexpectedly large layers.


Better Dockerfile for Python Flask

The earlier Dockerfile works for learning, but this version is closer to production practice.

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

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

COPY . .

EXPOSE 5000

ENV FLASK_APP=app.py

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

requirements.txt:

flask
flask-mysql

Why this is better

  • Uses a smaller Python base image
  • Separates dependency copy from source code copy
  • Improves Docker layer caching
  • Avoids unnecessary pip cache
  • Uses JSON-array command format

Build and Run the Custom Image

docker build -t my-flask-app:1.0 .

Run it:

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

Run in detached mode:

docker run -d --name flask-app -p 5000:5000 my-flask-app:1.0

Check logs:

docker logs -f flask-app

Dockerfile Best Practices

Do

  • Use specific base image versions.
  • Keep images small.
  • Use .dockerignore.
  • Copy dependency files before application source code.
  • Pin dependency versions where possible.
  • Use non-root users for production containers.
  • Keep one main process per container.
  • Use CMD or ENTRYPOINT clearly.
  • Scan images for vulnerabilities.
  • Push images to a trusted registry.

Don't

  • Do not use latest for production images.
  • Do not install unnecessary packages.
  • Do not copy secrets into images.
  • Do not run containers as root unless required.
  • Do not store application data inside the image.
  • Do not use huge base images when slim alternatives exist.
  • Do not combine unrelated services into one container.

.dockerignore

A .dockerignore file prevents unnecessary files from being copied into the build context.

Example:

.git
__pycache__
*.pyc
.env
node_modules
dist
build
*.log

Never copy secrets

Do not include .env, private keys, kubeconfig files, cloud credentials, or tokens inside Docker images.


Production-Ready Example

FROM python:3.12-slim

WORKDIR /app

RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

COPY requirements.txt .

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

COPY . .

USER appuser

EXPOSE 5000

ENV FLASK_APP=app.py
ENV PYTHONUNBUFFERED=1

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

Note

This Dockerfile uses a non-root user, smaller base image, and clean dependency installation.


Docker Image Naming

Image name format:

registry/user-or-project/image-name:tag

Examples:

myapp:1.0
mumshad/my-custom-app:1.0
registry.example.com/platform/myapp:2026.06.01

Tip

Use version tags that map to releases, Git commits, or CI/CD build numbers.

Examples:

docker build -t myapp:v1.0.0 .
docker build -t myapp:git-a1b2c3d .
docker build -t myapp:2026-06-01 .

Build Context

The . in this command is the build context:

docker build . -t myapp:1.0

Docker sends the build context to the Docker daemon.

Warning

A large build context slows down builds. Use .dockerignore to exclude unnecessary files.


Common Issues and Fixes

Issue Cause Fix
Build is slow Large context or no cache usage Add .dockerignore, order layers properly
Image is huge Large base image or unnecessary packages Use slim/alpine base, clean package cache
App not reachable App listens on localhost only Bind to 0.0.0.0
Dependency install repeats every build Source copied before dependencies Copy dependency file first
Secret leaked into image Secret copied during build Remove secret and rotate credentials
Container exits immediately No long-running process Check CMD/ENTRYPOINT and logs

Quick Commands

# Build image
docker build -t myapp:1.0 .

# Build using a specific Dockerfile
docker build -f Dockerfile -t myapp:1.0 .

# List images
docker images

# View image layers
docker history myapp:1.0

# Run image
docker run myapp:1.0

# Run with port mapping
docker run -p 5000:5000 myapp:1.0

# Tag image
docker tag myapp:1.0 username/myapp:1.0

# Login to registry
docker login

# Push image
docker push username/myapp:1.0

# Remove image
docker rmi myapp:1.0

Docker Images: Do and Don't

Do Don't
Use a clear Dockerfile Manually configure containers after startup
Pin versions Depend on latest
Keep image small Install unnecessary tools
Use build cache smartly Copy source before dependencies
Store config outside image Bake secrets into image
Push to trusted registry Share images from unknown sources
Scan images Ignore vulnerabilities

Revision Notes for CKA / DevOps

What is a Docker image?

A Docker image is a read-only template that contains application code, dependencies, runtime, and startup instructions.

What creates a Docker image?

A Dockerfile and the docker build command.

What is a Docker layer?

A layer is a filesystem change created by a Dockerfile instruction.

Why are layers useful?

Layers are cached and reused, making image rebuilds faster.

How do you push an image?

docker push username/image-name:tag

Remember

A Dockerfile is the repeatable build recipe. A Docker image is the packaged output. A container is the running instance.