- Dockerfile
- Building Docker Images
- Pushing Images to Docker Hub
- Layered Caching
- Basic Docker Commands
- Running Containers
- Finding Base Image Information
- Commands and Arguments
A Dockerfile is a text file that contains a set of instructions in command format. Each instruction tells Docker how to build a layer in your image. Dockerfiles provide a blueprint for creating Docker images.
Example Dockerfile:
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 runCommon Dockerfile Instructions:
FROM- Specifies the base image to start fromRUN- Executes commands during the build processCOPY- Copies files from the host into the imageADD- Similar to COPY but can also handle URLs and extract archivesENTRYPOINT- Sets the default command to run when the container startsCMD- Provides default arguments for the ENTRYPOINT or runs a commandWORKDIR- Sets the working directory for subsequent instructionsENV- Sets environment variablesEXPOSE- Documents which ports the container will listen on
Use the docker build command to create a Docker image from a Dockerfile.
Basic syntax:
docker build Dockerfile -t <username>/<image-name>Example:
docker build Dockerfile -t aravi/my-nameKey points:
- The
-tflag tags the image with a name (and optionally a tag like:v1.0) - You can specify the Dockerfile path (default is
Dockerfilein the current directory) - You can also use
docker build .to build from the current directory (Docker automatically looks for a file namedDockerfile)
Tagging with version:
docker build -t aravi/my-name:v1.0 .
docker build -t aravi/my-name:latest .After building an image, you can push it to a remote Docker registry like Docker Hub.
Push to Docker Hub:
docker push aravi/my-nameFull workflow:
-
Login to Docker Hub:
docker login
Enter your Docker Hub username and password
-
Build the image:
docker build -t aravi/my-name . -
Push the image:
docker push aravi/my-name
Important Notes:
- The image name must match your Docker Hub username (or organization) prefix
- Make sure you're logged in with
docker loginbefore pushing - The image will be publicly available (unless you have a private repository)
Docker uses a layered caching mechanism to speed up builds and efficiently manage image layers.
How it works:
- Each instruction in a Dockerfile creates a new layer in the image
- Docker caches the result of each layer after it's successfully built
- When you rebuild an image, Docker checks each layer from top to bottom
- If a layer hasn't changed, Docker reuses the cached layer instead of rebuilding it
- If a particular step fails, Docker will reuse all the previous successful layers from the cache
Example Dockerfile layers:
FROM Ubuntu # Layer 1: Base Ubuntu layer (120 MB)
RUN apt-get update # Layer 2: Changes in apt packages (306 MB)
RUN pip install flask # Layer 3: Changes in pip packages (6.3 MB)
COPY . /opt/source-code # Layer 4: Source code (229 B)
ENTRYPOINT flask run # Layer 5: Update Entrypoint (0 B)Benefits of layered caching:
- Faster builds - Unchanged layers are reused, saving time and resources
- Efficient storage - Layers are shared between images when they use the same base layers
- Incremental builds - If a step fails, you can fix it and rebuild without redoing successful steps
- Optimization strategy - Place frequently changing instructions (like
COPY .) at the end of the Dockerfile to maximize cache hits
Example scenario:
- You build an image successfully
- You make a small change to your source code
- You rebuild the image
- Docker reuses cached layers for
FROM,RUN, and other unchanged instructions - Only the
COPYand subsequent layers are rebuilt - This makes the rebuild much faster than building from scratch
Best practices for caching:
- Order Dockerfile instructions from least frequently changed to most frequently changed
- Place
COPYcommands that copy source code near the end (after dependency installations) - Combine
RUNcommands when possible to reduce layer count - Use
.dockerignoreto exclude unnecessary files from the build context
List running containers:
docker psShows all currently running containers with their IDs, images, status, ports, and names.
List all containers (including stopped):
docker ps -aShows all containers, both running and stopped. Useful for seeing containers that have exited or were stopped.
List Docker images:
docker imagesShows all Docker images stored locally on your system, including their repository name, tags, image IDs, creation date, and size.
The -p (or --publish) flag maps ports from the container to the host machine, allowing you to access services running inside the container from your host.
Syntax:
docker run -p <host_port>:<container_port> <image_name>Example:
docker run -p 8282:8080 aravi/my-nameExplanation:
<host_port>(8282) - The port on your host machine where you want to access the service<container_port>(8080) - The port inside the container where the application is listening<image_name>- The image name or tag that was specified with-tduring the build process
In this example, if your application inside the container listens on port 8080, you can access it from your host machine at localhost:8282. Traffic from port 8282 on your host will be forwarded to port 8080 inside the container.
Multiple port mappings:
docker run -p 8282:8080 -p 3000:3000 aravi/my-nameYou can map multiple ports by using multiple -p flags.
The -v (or --volume) flag mounts a directory from the host machine into the container, enabling persistent storage. When the Docker container exits or is removed, the data stored in the volume persists on the host machine.
Syntax:
docker run -v <host_directory>:<container_directory> <image_name>Example:
docker run -v /host/data:/container/data aravi/my-nameExplanation:
<host_directory>- The directory on your host machine (e.g.,/host/dataor./datafor current directory)<container_directory>- The directory inside the container where you want to mount the host directory (e.g.,/container/dataor/app/data)- This creates a bind mount that links the host directory to the container directory
Why use volumes:
- Data persistence - When the container is stopped or removed, data in the volume remains on the host
- Data sharing - Multiple containers can share the same volume
- Backup and migration - Since data is on the host, it's easier to backup and migrate
- Performance - Direct access to host filesystem can be faster than container filesystem
Example use case:
# Run a database container with persistent storage
docker run -v /my/host/data:/var/lib/mysql mysql:latest
# Even if the container stops or is removed, the database data remains in /my/host/data on your host machineUsing named volumes (Docker-managed):
docker run -v my-volume:/container/data aravi/my-nameDocker manages the volume location. You can list volumes with docker volume ls and inspect them with docker volume inspect my-volume.
You can run a command in a container to inspect the base image or OS information without starting a long-running container.
Example:
docker run python:3.6 cat /etc/*release*Explanation:
docker run python:3.6- Runs a container from thepython:3.6imagecat /etc/*release*- Executes a command that displays OS release information- The container starts, runs the command, displays the output, and then exits
This is useful for:
- Finding out which Linux distribution an image is based on
- Checking OS version information
- Inspecting system configuration without needing to interactively enter the container
- Understanding what base image was used in a Dockerfile
Other useful inspection commands:
# Check OS version
docker run python:3.6 cat /etc/os-release
# List installed packages (Debian/Ubuntu)
docker run python:3.6 dpkg -l
# Check kernel version
docker run python:3.6 uname -aThese commands run and exit immediately, making them perfect for quick inspections without leaving containers running.
A Docker container lives only for as long as the process inside it is running. Once the main process exits, the container stops.
Example:
docker psIf no containers are running, this shows nothing. Containers exit when their main process completes or terminates.
Key concept:
- Containers are not virtual machines - they're processes
- When the process exits, the container stops
- A stopped container appears in
docker ps -abut not indocker ps
Running Ubuntu container:
docker run ubuntuWhat happens:
- The Ubuntu image has a default
CMD ["bash"]instruction - When you run the container, it tries to start the bash program
- Bash looks for a terminal (TTY) to attach itself to
- The Docker container doesn't have an interactive terminal by default
- The bash process immediately exits because it can't find a terminal
- The container exits immediately
This is why:
docker ps
# Shows nothing - container has exitedYou can override the default command when running a container by specifying a command after the image name.
Example:
docker run ubuntu sleep 10This runs the sleep 10 command instead of the default bash command. The container will:
- Start
- Run
sleep 10 - Wait for 10 seconds
- Exit when sleep completes
This works, but you have to specify the command every time you run the container.
The CMD instruction in a Dockerfile sets the default command that will run when the container starts. You can override it at runtime by providing a command.
Syntax options:
# Shell form
CMD sleep 5
# JSON array form (recommended)
CMD ["sleep", "5"]Example Dockerfile:
FROM ubuntu
CMD ["sleep", "5"]Building and running:
docker build -t ubuntu-sleeper .
docker run ubuntu-sleeperThe container will sleep for 5 seconds and then exit.
Overriding CMD at runtime:
docker run ubuntu-sleeper sleep 10This overrides the default sleep 5 with sleep 10. The container will sleep for 10 seconds instead.
Key behavior with CMD:
- CMD gets completely replaced when you provide a command at runtime
- Whatever you specify after the image name replaces the entire CMD instruction
docker run ubuntu-sleeper sleep 10replaces["sleep", "5"]withsleep 10
The ENTRYPOINT instruction sets the main command that will always run. Unlike CMD, arguments provided at runtime get appended to the ENTRYPOINT, not replaced.
Syntax:
ENTRYPOINT ["sleep"]Example Dockerfile:
FROM ubuntu
ENTRYPOINT ["sleep"]Running with argument:
docker run ubuntu-sleeper 10This appends 10 as an argument to sleep, effectively running sleep 10.
Key behavior with ENTRYPOINT:
- ENTRYPOINT appends arguments - whatever you provide gets added to the entrypoint command
docker run ubuntu-sleeper 10becomessleep 10docker run ubuntu-sleeper 20becomessleep 20
What happens if you don't provide an argument:
docker run ubuntu-sleeperThis would run just sleep with no arguments, resulting in an error: "operand is missing" (sleep requires a number).
You can use both ENTRYPOINT and CMD together. ENTRYPOINT provides the base command, and CMD provides default arguments. If you provide arguments at runtime, they replace the CMD default arguments.
Example Dockerfile:
FROM ubuntu
ENTRYPOINT ["sleep"]
CMD ["5"]Important: Both must be in JSON array format for this to work correctly.
Running without arguments:
docker run ubuntu-sleeperThis uses the default CMD value, running sleep 5.
Running with custom argument:
docker run ubuntu-sleeper 10This replaces the CMD default (5) with 10, running sleep 10.
How it works:
ENTRYPOINT ["sleep"]- Base command (always runs)CMD ["5"]- Default argument (can be overridden)- Combined:
sleep 5(by default) - With override:
sleep 10(when you provide10)
Summary:
- ENTRYPOINT = The command that always runs (base command)
- CMD = Default arguments that can be overridden
- Runtime arguments replace CMD, but ENTRYPOINT remains
To override both ENTRYPOINT and CMD, use the --entrypoint flag.
Syntax:
docker run --entrypoint <new_command> <image_name> <arguments>Example:
docker run --entrypoint sleep2.0 ubuntu-sleeper 10This:
- Overrides the ENTRYPOINT from
sleeptosleep2.0 - Passes
10as an argument tosleep2.0 - Effectively runs:
sleep2.0 10
Use cases:
- When you need to completely change the entrypoint command
- For debugging or testing different entry points
- When the default entrypoint doesn't work for your use case
Summary table:
| Instruction | Runtime Override Behavior | Example |
|---|---|---|
CMD ["sleep", "5"] |
Completely replaced | docker run image sleep 10 → runs sleep 10 |
ENTRYPOINT ["sleep"] |
Arguments appended | docker run image 10 → runs sleep 10 |
ENTRYPOINT ["sleep"] + CMD ["5"] |
CMD replaced, ENTRYPOINT kept | docker run image 10 → runs sleep 10 |
--entrypoint flag |
Both overridden | docker run --entrypoint cmd image arg → runs cmd arg |
Dockerfile for Nginx:
FROM ubuntu
CMD ["nginx"]When you run docker run nginx-image, it starts the nginx server. The container stays running as long as nginx is running.
Container lifecycle:
- Container starts → nginx starts
- Container runs → nginx runs (serving web requests)
- Container stops → when nginx process stops or is terminated