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:
- Start with Ubuntu
- Update package repositories
- Install OS dependencies
- Install Python dependencies
- Copy application source code
- 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:
Example:
Here:
FROMis the instructionubuntuis 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
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.
Runs commands while building the image.
This is commonly used to install operating system packages.
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.
Copies application files from the current directory on the host into the image.
Build a Docker Image
Use docker build to create an image from a Dockerfile.
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 |
Production practice
Always tag images with meaningful versions.
Push Image to Registry
After building an image locally, push it to a registry so other systems can pull and run it.
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.
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.
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:
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
Run it:
Run in detached mode:
Check logs:
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
CMDorENTRYPOINTclearly. - Scan images for vulnerabilities.
- Push images to a trusted registry.
Don't
- Do not use
latestfor 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:
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:
Examples:
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 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.
Remember
A Dockerfile is the repeatable build recipe. A Docker image is the packaged output. A container is the running instance.