What a Docker image is and why you build one
A Docker image is a packaged version of your process that includes everything it needs to run: the code, the runtime, libraries, and configuration files. When you build an image, you are creating a template that Docker can use to spin up containers — the actual running instances of your process.
You build an image because it solves a real problem: your process works on your machine, but when someone else runs it, they get errors because they have different software versions, missing libraries, or a different operating system. An image locks all of that down. Anyone who has Docker installed can run your image and get the exact same environment you tested in.
Building an image is also how you prepare your process to run on a server, in the cloud, or across multiple machines. Instead of installing software manually on each server, you build the image once and deploy it everywhere.
Key Takeaways
- A Docker image is built from a text file called a Dockerfile that lists the base operating system, the software to install, and the commands to run your process.
- You write a Dockerfile in your project folder, then run the docker build command to create the image, which Docker stores locally on your machine.
- The image is built in layers, where each instruction in the Dockerfile creates a new layer; Docker caches these layers so rebuilding is faster if you only change the last few steps.
- Once built, you can run the image as a container, share it with others by pushing it to a registry like Docker Hub, or deploy it to a server.
- Common mistakes include not excluding unnecessary files with a .dockerignore file, running the process as root instead of a limited user, and not testing the image before sharing it.
Creating a Dockerfile for your process
A Dockerfile is a plain text file with instructions that tell Docker how to build your image. You create it in the root folder of your project and name it exactly Dockerfile with no file extension.
The Dockerfile starts with a FROM instruction that specifies the base image — usually a lightweight operating system with a runtime already installed. For a Python process, you might use FROM python:3.11-slim, which gives you Debian Linux with Python 3.11 already set up. For a Node.js process, you might use FROM node:18-alpine. The -slim and -alpine tags mean a smaller, faster base image.
After the base image, you add instructions to install dependencies, copy your code, and set the command that runs when the container starts. Here is a straightforward example for a Python Flask web process:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"]
The WORKDIR instruction sets the folder inside the container where your code will live. COPY requirements.txt . copies your Python dependencies file from your machine into the container. RUN pip install -r requirements.txt installs those dependencies. COPY . . copies the rest of your process code. CMD specifies the command that runs when the container starts — in this case, running your Flask app.
Running the docker build command
Once your Dockerfile is written, you build the image by opening a terminal in your project folder and running the docker build command. The basic syntax is:
docker build -t myapp:1.0 .
The -t flag sets the name and tag for your image. myapp is the name, and 1.0 is the tag — usually a version number. The dot at the end tells Docker to look for the Dockerfile in the current folder. Docker will read the Dockerfile, execute each instruction in order, and create the image on your machine.
The build process takes longer the first time because Docker has to read the base image and install dependencies. When you build again after making small changes, Docker reuses the layers it cached from the previous build, so it finishes much faster. This is why the order of instructions in your Dockerfile matters: put things that change often (like your process code) near the end, and things that change rarely (like the base image and system dependencies) near the beginning.
If the build fails, Docker shows you which instruction caused the error. Common failures include a typo in a command, a missing file, or a dependency that does not exist for your operating system. Fix the Dockerfile and run docker build again.
Testing the image by running a container
After the build finishes successfully, you test the image by running it as a container. Use the docker run command:
docker run -p 5000:5000 myapp:1.0
The -p 5000:5000 flag maps port 5000 on your machine to port 5000 inside the container, so you can access the process in your web browser at localhost:5000. If your process listens on a different port, change both numbers. The image name and tag must match what you used in the build command.
Docker starts the container and shows you the output from your process. If your Flask app prints "Running on http://127.0.0.1:5000", you can open that address in your browser and test the process. If something is wrong — the app crashes, does not start, or behaves differently than it did on your machine — you can stop the container by pressing Ctrl+C and fix the Dockerfile.
Once you confirm the container works as expected, you are ready to share the image or deploy it to a server.
Sharing your image with Docker Hub or a private registry
To share your image with others or deploy it to a cloud server, you push it to a registry — a repository where Docker images are stored. Docker Hub is the public registry run by Docker itself. Private registries exist for organizations that want to keep images internal.
To push to Docker Hub, you first create an account at hub.docker.com. Then you log in from your terminal:
docker login
Docker asks for your username and password. After you log in, you tag your image with your Docker Hub username so Docker knows where to push it:
docker tag myapp:1.0 yourusername/myapp:1.0
Then push the image:
docker push yourusername/myapp:1.0
Docker uploads the image to Docker Hub. Anyone can now pull and run your image by using docker run yourusername/myapp:1.0, as long as the image is set to public. If you want to keep it private, you can change the visibility in your Docker Hub settings, and only people you give access to can pull it.
Common mistakes and how to avoid them
One frequent mistake is copying unnecessary files into the image, which makes it larger and slower to push and pull. Create a .dockerignore file in your project folder and list files and folders to exclude, just like a .gitignore file. For example:
__pycache__ .git node_modules .env *.pyc
This prevents Docker from copying Python cache files, the Git history, Node modules, environment variable files, and compiled Python files into the image.
Another mistake is running your process as the root user inside the container. If an attacker compromises your process, they gain root access to the container. Instead, create a limited user in your Dockerfile:
RUN useradd -m appuser USER appuser
This creates a user called appuser and switches to that user before running your process. The process runs with limited permissions.
A third mistake is not testing the image before pushing it to a registry or deploying it to production. Always run the image locally with docker run and verify that your process starts correctly and behaves as expected. Test the features you changed, not just whether the container starts.
Frequently Asked Questions
What is the difference between a Docker image and a container?
An image is a template or blueprint — a file that sits on your machine. A container is a running instance of that image. You can build one image and run it as multiple containers at the same time, each with its own isolated environment. Think of the image as a recipe and the container as a meal made from that recipe.
Do I need to rebuild the image every time I change my code?
Yes, you need to rebuild the image to include the new code. However, Docker caches the layers from your previous build, so if you only changed your process code and not the dependencies or base image, the rebuild is fast. This is why putting COPY . . near the end of the Dockerfile is important.
Can I run a Docker image without pushing it to a registry?
Yes. If the image is built on your machine, you can run it locally with docker run. You only need to push to a registry if you want to share it with others, deploy it to a different machine, or keep a backup. For local development and testing, building and running locally is fine.
What does the tag in an image name mean?
The tag is a label, usually a version number like 1.0 or latest. You can have multiple tags pointing to the same image, and you can rebuild with a new tag each time you make changes. This lets you keep old versions around and roll back if needed. If you do not specify a tag, Docker uses latest by default, but it is better to be explicit.
Why is my image so large?
Large images usually come from including unnecessary files, using a heavy base image, or installing development tools that are not needed at runtime. Use a .dockerignore file to exclude cache and build files. Choose a slim or alpine base image instead of a full operating system. Remove build dependencies after you use them in the same RUN instruction to keep the layer small.