How to Build Docker Images with Dockerfile

When you want an application to run the same way on your laptop, a test server, and production, you package it as a Docker image. The image contains the application and everything it needs to run. A container is a runtime instance of that image.
Images are built from a Dockerfile, a plain text file with the build instructions. In this tutorial, we will explain what a Dockerfile is, how to create one, and how to build a Docker image from it. The walkthrough uses an Ubuntu 26.04 example, so you can follow the full process from Dockerfile to running container.
Quick Reference
For a printable quick reference, see the Docker cheatsheet .
| Command | Description |
|---|---|
docker build -t name . | Build image from Dockerfile in current directory |
docker build -t name:tag . | Build with a specific tag |
docker build -f path/Dockerfile . | Use a custom Dockerfile location |
docker build --no-cache -t name . | Build without using the layer cache |
docker build --build-arg VAR=val . | Pass a build-time argument |
docker image ls | List all local images |
docker rmi image-name | Remove an image |
docker run -d -p host:container image | Run a container from an image |
What is Dockerfile
A Dockerfile is a text file that contains all the commands a user could run on the command line to create an image. It includes all the instructions needed by Docker to build the image.
Docker images are made up of a series of filesystem layers representing instructions in the image’s Dockerfile that make up an executable software application.
The Dockerfile takes the following form:
# Comment
INSTRUCTION argumentsINSTRUCTION is not case-sensitive, but the convention is to use UPPERCASE for its names.
Below is the list with a short description of some of the most used Dockerfile instructions:
- ARG - This instruction allows you to define variables that can be passed at build-time. You can also set a default value.
- FROM - The base image for building a new image. This instruction must be the first non-comment instruction in the Dockerfile. Each additional
FROMstarts a new build stage. The only exception from this rule is when you want to use a variable in theFROMargument. In this case,FROMcan be preceded by one or moreARGinstructions. - LABEL - Used to add metadata to an image, such as description, version, author, etc. You can specify more than one
LABEL, and eachLABELinstruction is a key-value pair. - RUN - The commands specified in this instruction will be executed during the build process. Each
RUNinstruction creates a new layer on top of the current image. - ADD - Used to copy files and directories from the specified source to the specified destination on the Docker image. The source can be local files or directories or a URL. If the source is a local tar archive, then it is automatically unpacked into the Docker image.
- COPY - Similar to
ADD, but it never unpacks archives and cannot fetch remote sources. It can copy from the build context, from another build stage, or from another image using--from. - ENV - This instruction allows you to define an environment variable.
- CMD - Used to specify the default command that will be executed when you run a container. If the Dockerfile contains more than one
CMDinstruction, only the last one takes effect. - ENTRYPOINT - Similar to
CMD, this instruction defines what command will be executed when running a container. - WORKDIR - This directive sets the current working directory
for the
RUN,CMD,ENTRYPOINT,COPY, andADDinstructions. - USER - Sets the username or
UIDfor subsequentRUNinstructions in the current stage and forCMDandENTRYPOINTat runtime. Use--chownwithCOPYorADDto set file ownership. - VOLUME - Creates a mount point for data that should be stored outside the container filesystem. The Dockerfile cannot set a host directory. You choose it with the
-voption when you run the container. - EXPOSE - Documents the port on which the container listens at runtime. It does not publish the port. Publishing is done with the
-poption ofdocker run.
For a complete reference and detailed explanation of Dockerfile instructions, see the official Dockerfile reference page.
Ubuntu 26.04 Dockerfile Example
The most common scenario when creating Docker images is to pull an existing image from a registry (usually from Docker Hub) and specify the changes you want to make on the base image. Popular base images include Alpine, which is very small, and full distribution images such as Ubuntu and Debian.
In this example, we will create a Docker image for the Redis server. We will use Ubuntu 26.04 as a base image.
First, create a directory that will contain the Dockerfile and all the necessary files:
mkdir ~/redis_dockerNavigate to the directory and create the following Dockerfile:
cd ~/redis_docker
nano DockerfileFROM ubuntu:26.04
RUN apt-get update && \
apt-get install -y --no-install-recommends redis-server && \
rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["redis-server", "--protected-mode", "no"]Here is what each line in the Dockerfile does:
- Line
1defines the base image. - The
RUNinstruction on line3updates the apt index, installs theredis-serverpackage without recommended packages, and removes the downloaded package lists to keep the image smaller. - The
EXPOSEinstruction documents the port on which the Redis server listens. - The
CMDinstruction on the last line sets the default command that will be executed when the container starts. Protected mode is disabled so Redis accepts connections from outside the container.
Save the file and close the editor.
Building the Image
The next step is to build the image. To do so, run the following command from the directory where the Dockerfile is located:
docker build -t linuxize/redis .The option -t specifies the image name and optionally a username and tag in the username/imagename:tag format. The dot at the end is the build context.
On current Docker versions, docker build uses the BuildKit builder. The output of the build process will look something like this:
[+] Building 21.2s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 236B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:26.04 1.5s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/ubuntu:26.04@sha256:f144425ff09be612d6d9 9.5s
=> => resolve docker.io/library/ubuntu:26.04@sha256:f144425ff09be612d6d9 0.0s
=> => sha256:5fa77813dc560f299656013d742628128cac51fdfda9793 392B / 392B 0.2s
=> => sha256:06db5ba23986104cd78dcfa853ddf2354c943d2ef 40.86MB / 40.86MB 9.0s
=> => extracting sha256:06db5ba23986104cd78dcfa853ddf2354c943d2efefc333e 0.5s
=> => extracting sha256:5fa77813dc560f299656013d742628128cac51fdfda97934 0.0s
=> [2/2] RUN apt-get update && apt-get install -y --no-install-recom 9.5s
=> exporting to image 0.7s
=> => exporting layers 0.2s
=> => exporting manifest sha256:905e87a396b9f60919e41bb18b855259b2908f06 0.0s
=> => exporting config sha256:0ba7cf572ca745e2b3df3554d47ea4b2f9472ce03f 0.0s
=> => exporting attestation manifest sha256:4e547b8d7590e0bb18605ef66ab6 0.0s
=> => exporting manifest list sha256:53296c6a1d2bf33ddaba24a940af91f14e4 0.0s
=> => naming to docker.io/linuxize/redis:latest 0.0s
=> => unpacking to docker.io/linuxize/redis:latest 0.5sEach [1/2] and [2/2] line is one step from the Dockerfile. The first step pulls the ubuntu:26.04 base image, and the second runs the RUN instruction. The last lines show the image being exported and tagged as linuxize/redis:latest.
When the build process is completed, the new image will be listed in the image list:
docker image lsIMAGE ID DISK USAGE CONTENT SIZE EXTRA
linuxize/redis:latest 53296c6a1d2b 189MB 44.4MBDISK USAGE is the total space used by the stored image, including both compressed content and unpacked layers when using the containerd image store. CONTENT SIZE is the size of the compressed image content. Docker versions older than 29 print a different table with REPOSITORY, TAG, and SIZE columns.
If you want to push the image to Docker Hub, see Push images to a repository .
Build Context and Custom Dockerfile Path
When you run docker build, the last argument is the build context. Local source paths for COPY and ADD are relative to the root of this context and cannot access host files outside it. COPY --from can read from other build stages, images, or named contexts, and ADD can fetch remote URLs.
By default, Docker looks for a file named Dockerfile in the root of the context. To use a Dockerfile with a different name or location, specify its path with the -f option:
docker build -f Dockerfile.dev -t linuxize/redis:dev .The context and the Dockerfile do not have to be in the same place. The following command builds from the ~/redis_docker directory while you are in your home directory:
docker build -f redis_docker/Dockerfile -t linuxize/redis redis_dockerRunning a Container
This example requires Docker 28.0 or later because older versions can expose ports published to localhost to other devices on the same local network. See Docker’s port publishing documentation for details. To run a container from the image, use:
docker run -d -p 127.0.0.1:6379:6379 --name redis linuxize/redisThe -d option tells Docker to run the container in detached mode, the -p 127.0.0.1:6379:6379 option publishes port 6379 on the host loopback interface, and the --name redis option specifies the container name. The last argument linuxize/redis is the name of the image used to run the container. For more options, see our guide on the docker run command
.
127.0.0.1. A port published as -p 6379:6379 listens on all interfaces. Docker adds its own firewall rules, and the port is reachable from the network even when UFW blocks it. If you set a password with requirepass, keep it in an .env file that is listed in .gitignore, not in the Dockerfile.When the container starts, use the following command to list all running containers :
docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
42723a119aab linuxize/redis "redis-server --prot…" 3 seconds ago Up 3 seconds 127.0.0.1:6379->6379/tcp redisThe PORTS column shows that port 6379 is published on 127.0.0.1 only.
To verify that everything works as it should, run redis-cli inside the container with docker exec
:
docker exec redis redis-cli pingPONGThe Redis server responds with PONG, which means it is running and accepting connections.
Excluding Files with .dockerignore
BuildKit transfers only changed files between builds and skips files that the build does not use. A broad instruction such as COPY . . can still include large directories such as .git or node_modules, increasing transfer time and image size. Files such as .env can also end up in the image if a COPY instruction picks them up.
To exclude files and directories, create a .dockerignore file in the root of the build context:
.git
node_modules
*.log
.envEach line is a pattern that matches files or directories relative to the context. The syntax is similar to the one of Git’s .gitignore file
.
Multi-Stage Builds
A multi-stage build uses more than one FROM instruction in the same Dockerfile. Each FROM starts a new stage, and you can copy files from one stage into another. This way, you can compile an application in an image with the full toolchain and ship only the result.
The following example builds a small Go program. Create a new directory named hello with a main.go file:
package main
import "fmt"
func main() {
fmt.Println("Hello from a multi-stage build")
}In the same directory, create the following Dockerfile:
FROM golang:1.26 AS build
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 go build -o /hello main.go
FROM scratch
COPY --from=build /hello /hello
CMD ["/hello"]The first stage starts from the golang:1.26 image and names the stage build. It copies main.go into /src and compiles a static binary. The second stage starts from scratch, an empty image, and copies only the compiled binary from the build stage.
Build the image and run it:
docker build -t linuxize/hello .
docker run --rm linuxize/helloHello from a multi-stage buildList the images to compare the sizes:
docker image lsIMAGE ID DISK USAGE CONTENT SIZE EXTRA
linuxize/hello:latest 85de01c2dd1e 3.67MB 1.33MB
linuxize/redis:latest 53296c6a1d2b 189MB 44.4MB UThe linuxize/hello image is only 3.67MB. The Go compiler from the first stage is not part of the final image. The U in the EXTRA column means the image is in use by a container. For more examples, see the official multi-stage builds
documentation.
Troubleshooting
Cannot connect to the Docker daemon or permission denied while trying to connect to the docker API
The first message means the Docker service is not running. Start it with sudo systemctl start docker. The second message means your user does not have permission to access the Docker socket. Add your user to the docker group with sudo usermod -aG docker $USER and log out and back in. Members of the docker group have root-level access to the host, so add only trusted users.
failed to resolve source metadata or pull access denied
The base image in FROM does not exist or the tag is incorrect, for example ubuntu:26.4 instead of ubuntu:26.04. Check the image name and tag on Docker Hub
and update the FROM line accordingly.
'docker buildx build' requires 1 argument
The build context is missing from the command. Add the dot at the end to use the current directory: docker build -t name ..
Build succeeds but changes are not reflected
Changes to files used by COPY or ADD invalidate the affected cached layers automatically. An unchanged RUN instruction, such as one that installs packages, can still reuse an old result. Run docker build --no-cache -t name . if you need those commands to execute again. Recreate an existing container to use the rebuilt image.
failed to read dockerfile: open Dockerfile: no such file or directory
The build context does not contain a file named Dockerfile. Run the command from the directory containing the Dockerfile, or specify its path with -f. For example: docker build -f path/to/Dockerfile ..
FAQ
What is the difference between CMD and ENTRYPOINT?ENTRYPOINT defines the main executable that always runs when the container starts. CMD provides default arguments that can be overridden at runtime. Use ENTRYPOINT for the command and CMD for its default flags, or use CMD alone for a fully overridable default command.
What is the difference between COPY and ADD?
Prefer COPY for copying local files and directories, since it does exactly what it says. Use ADD only when you need its extra features: fetching a remote URL or auto-extracting a local tar archive. Using COPY makes Dockerfiles easier to reason about. See our full comparison of Dockerfile COPY vs ADD
for the details.
How do I reduce the size of a Docker image?
Use a smaller base image such as alpine instead of ubuntu. Combine RUN commands into a single layer and remove the package lists in the same step (as shown in the example above). For compiled applications, use a multi-stage build to keep only the runtime artifacts in the final image.
Conclusion
This tutorial covered the basics of using Dockerfiles to build Docker images and run containers from them. To learn more about recommended best practices, see Best practices for writing Dockerfiles , and to run several containers together, see our guide on Docker Compose .
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page