What a Docker image is and why you build one
A Docker image is a blueprint — a frozen snapshot of everything your process needs to run. It contains your code, the operating system layer it runs on, all the libraries and tools it depends on, and the instructions for how to start it. When you build an image, you are packaging all of that into a single file that can run the same way on your laptop, a coworker's machine, or a server in the cloud.
You build an image so that you do not have to say "it works on my machine" ever again. Once the image exists, anyone who has Docker installed can run it without installing Node.js or Python or PostgreSQL or any of the other things your app needs. The image carries all of that with it.
Building an image is not hard, but it does require you to think through what your process actually needs — what files, what software, what commands run when it starts. That thinking is the real work. The actual build command is one line.
Key Takeaways
- A Dockerfile is a text file that lists the base operating system, the files to copy in, the software to install, and the command that starts your process.
- You write the Dockerfile in your project folder, then run docker build to turn it into an image that Docker can run.
- The image is built in layers — each instruction in the Dockerfile creates a new layer — and Docker caches layers so rebuilds are faster when you change only your code.
- You tag the image with a name and version number so you can refer to it later and push it to a registry if you want to share it.
The Dockerfile: what goes in it and why
A Dockerfile is a plain text file with no extension. It lives in the root of your project folder. Each line is an instruction that tells Docker what to do when building the image.
The first instruction is almost always FROM, which specifies the base image — the starting operating system and runtime. If you are building a Python process, you might write FROM python:3.11-slim. That pulls in a minimal Linux system with Python 3.11 already installed. If you are building a Node.js app, you might write FROM node:18-alpine. The word after the colon (3.11-slim, 18-alpine) is the tag, which specifies the exact version.
After FROM, you typically use WORKDIR to set the working directory inside the container — the folder where your code will live. WORKDIR /app creates that folder and makes it the default location for all commands that follow.
Then you use COPY to bring files from your computer into the image. COPY . . copies everything from your current folder into the /app folder inside the image. You might also use RUN to execute commands — for example, RUN pip install -r requirements.txt installs Python dependencies listed in a requirements.txt file.
Finally, you use CMD or ENTRYPOINT to specify the command that runs when someone starts a container from your image. CMD ["python", "app.py"] tells Docker to run the Python interpreter on your app.py file.
A concrete example: building a Python web process
Say you have a straightforward Python web app in a folder called my-app. The folder contains app.py (your code), requirements.txt (your dependencies), and a templates folder (your HTML files). You want to build a Docker image so others can run it without installing Python.
Create a file called Dockerfile in that folder with these contents:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"]
This Dockerfile says: start with Python 3.11 on a minimal Linux system, set the working directory to /app, copy in requirements.txt, install the dependencies, copy in everything else, and when the container starts, run python app.py.
Now open a terminal in the my-app folder and run:
docker build -t my-app:1.0 .
The -t flag tags the image with a name (my-app) and version (1.0). The dot at the end tells Docker to look for the Dockerfile in the current folder. Docker will read the Dockerfile, execute each instruction, and create an image. The first time you build, it takes a minute or two because it has to read the base Python image and install dependencies. The second time, it is much faster because Docker caches the layers.
How Docker builds images in layers
Docker does not execute your Dockerfile all at once. It builds the image in layers, one instruction at a time. Each instruction creates a new layer on top of the previous one. The FROM instruction creates the first layer (the base operating system). The COPY instruction creates another layer (your files added on top). The RUN instruction creates another layer (the installed dependencies on top of that).
This layering is why Docker is efficient. If you change only your app.py file and rebuild, Docker does not re-read the Python base image or re-run pip install. It reuses the cached layers from the previous build and only rebuilds the layers that changed. That is why the second build is so much faster.
The order of instructions matters for this reason. Put things that change rarely (like the base image and dependencies) early in the Dockerfile, and things that change often (like your code) later. That way, rebuilds skip the expensive steps.
Tagging and naming your image
When you build an image, you give it a name and a version number (called a tag). The format is name:version. my-app:1.0 is a name and version. my-app:latest is a name with the tag "latest", which is a convention for the most recent version.
You can also tag an image with a registry name if you plan to push it to Docker Hub or another registry. docker.io/my-username/my-app:1.0 tells Docker that this image belongs to your-username on Docker Hub. You do not need a registry name if you are only running the image locally.
To tag an image after you have already built it, use docker tag:
docker tag my-app:1.0 my-app:latest
This creates a second tag pointing to the same image. Both my-app:1.0 and my-app:latest now refer to the same image file.
Common mistakes and how to avoid them
The most common mistake is copying too much into the image. If you copy your entire project folder with COPY . ., you are also copying node_modules, .git, temporary files, and anything else in that folder. Use a .dockerignore file (similar to .gitignore) to exclude files you do not need. List one filename or pattern per line: node_modules, .git, __pycache__, .env.
Another mistake is running commands as root when you should run them as a regular user. By default, everything in a container runs as root, which is a security risk if the container is compromised. Use RUN useradd -m appuser to create a user, then USER appuser to switch to that user before running your process.
A third mistake is making the image too large. If your base image is ubuntu (which is 77 megabytes) instead of alpine or slim (which are 5 to 12 megabytes), your image will be much bigger and slower to read and start. Use the smallest base image that has what you need.
Running a container from your image
Once you have built an image, you run it with docker run:
docker run my-app:1.0
This starts a container from your image. The container runs the CMD instruction you specified in the Dockerfile — in the Python example above, it runs python app.py.
If your process listens on a port (like port 5000 for a web server), you need to map that port from the container to your computer so you can reach it. Use the -p flag:
docker run -p 5000:5000 my-app:1.0
This maps port 5000 inside the container to port 5000 on your computer. If you want to use a different port on your computer, write -p 8000:5000 to map port 8000 on your computer to port 5000 in the container.
Frequently Asked Questions
Do I have to write a Dockerfile by hand, or can I generate one?
You can write one by hand — it is just a text file — or you can use tools that generate one for you. Some frameworks like Django and Express have documentation that includes a sample Dockerfile. You can also ask an AI tool to write one based on your project structure. Either way, you should read and understand what it does before you use it.
What is the difference between an image and a container?
An image is the blueprint — the file you build and store. A container is a running instance of that image. You can have one image and run ten containers from it, each with its own isolated filesystem and processes. When you stop a container, the image is still there and you can run another container from it.
Can I build an image on my Mac and run it on a Linux server?
Yes. Docker images are portable across operating systems. An image built on Mac will run on Linux, Windows, or any other system that has Docker installed. That is the whole point — the image carries everything it needs, so the host operating system does not matter.
How do I see what images I have built?
Run docker images to list all images on your computer. It shows the name, tag, image ID, when it was created, and its size. Run docker rmi image-name:tag to delete an image you no longer need.
What happens if my Dockerfile has an error?
Docker will stop the build and show you an error message. The message usually says which line failed and why — for example, "file not found" if you tried to copy a file that does not exist. Fix the error in the Dockerfile and run docker build again.