What you need to do to build a Docker image

A Docker image is a blueprint that contains your application code, its dependencies, and the operating system layer it needs to run. To create one, you write a text file called a Dockerfile that lists the steps Docker should follow, then run the docker build command to turn that file into an image you can run as a container.

The process has three main parts: write a Dockerfile with instructions, build the image from that file, and verify it works by running it as a container. Most images start from an existing base image (like Ubuntu or Node.js) rather than from scratch, which saves you from installing the operating system and common tools yourself.

Key Takeaways

  • A Dockerfile is a plain text file with instructions that tell Docker how to assemble your image, starting from a base image and adding your application on top.
  • The docker build command reads your Dockerfile and creates an image, which you tag with a name and version so you can refer to it later.
  • You run docker run with your image name to start a container and test that everything works before pushing the image to a registry.
  • Base images like node:18 or python:3.11 already include the language runtime and common tools, so you only add what your specific application needs.
  • Each line in a Dockerfile creates a layer, and Docker caches layers, so ordering your instructions from least-changed to most-changed makes rebuilds faster.

Writing your Dockerfile

Create a file named Dockerfile (no extension) in your project root. Start with a FROM instruction that names the base image you want to build on. For a Node.js application, you might write FROM node:18. For Python, FROM python:3.11. Docker Hub hosts thousands of base images; search there to find one that matches your language and version.

After the base image, add a WORKDIR instruction to set the working directory inside the container — this is where your application files will live. Then use COPY to copy files from your computer into the image. Most applications copy a package file first (like package.json for Node or requirements.txt for Python), then run an install command, then copy the rest of the application code. This order matters because Docker caches each layer; if you change your application code but not your dependencies, Docker can reuse the cached dependency layer and rebuild faster.

After copying files and installing dependencies, use RUN to execute any other setup commands your application needs. Then use EXPOSE to document which port your application listens on (this does not actually open the port; it is informational). Finally, use CMD or ENTRYPOINT to specify the command Docker should run when the container starts.

Here is a simple example for a Node.js application:

FROM node:18 WORKDIR /app COPY package.json package-lock.json . RUN npm install COPY . . EXPOSE 3000 CMD ["node", "server.js"]

Building the image with docker build

Open a terminal in your project directory where the Dockerfile sits. Run docker build -t myapp:1.0 . The -t flag tags your image with a name and version; myapp:1.0 means the image is called myapp and the version is 1.0. The dot at the end tells Docker to look for the Dockerfile in the current directory.

Docker will read each line of your Dockerfile and execute it, printing progress to the terminal. If any step fails, the build stops and shows you the error. Common failures include a base image that does not exist, a file path that is wrong, or a command that fails inside the container. Read the error message carefully; it usually points to the exact line that broke.

Once the build completes, Docker stores the image locally on your computer. You can list all your images by running docker images and you should see myapp:1.0 in the list with its size and creation time.

Testing your image by running a container

Before you push your image anywhere or use it in production, run it as a container to make sure it works. Use docker run -p 3000:3000 myapp:1.0 The -p flag maps port 3000 on your computer to port 3000 inside the container, so you can reach your application at localhost:3000 in your browser or with curl.

If your application needs environment variables, pass them with -e: docker run -e DATABASE_URL=postgres://... -p 3000:3000 myapp:1.0 If it needs to write files or store data, use -v to mount a volume: docker run -v /data:/app/data -p 3000:3000 myapp:1.0

Watch the output to see if your application starts without errors. If it crashes or hangs, check the logs with docker logs followed by the container ID (which Docker prints when you start it). Once you confirm the container runs correctly, stop it by pressing Ctrl+C in the terminal.

Reducing image size and build time

Docker images can grow large if you are not careful. Use a .dockerignore file to exclude files you do not need in the image, the same way .gitignore works for Git. List node_modules, .git, test files, and any other files your application does not need at runtime. This keeps your image smaller and speeds up the build because Docker does not have to copy unnecessary files.

Choose a slim or alpine base image if you want a smaller starting point. node:18-alpine is much smaller than node:18 because it uses Alpine Linux instead of Debian, though it has fewer tools pre-installed. For most applications, alpine is fine and saves significant space.

Order your Dockerfile instructions from least-changed to most-changed. Dependencies change less often than application code, so copy and install dependencies before copying your application files. This way, when you rebuild after changing only your code, Docker reuses the cached dependency layer and only rebuilds the application layer.

Pushing your image to a registry

Once your image works, you can push it to a registry so others can pull and run it. Docker Hub is the most common public registry. Create a free account there, then log in from your terminal with docker login and enter your username and password.

Tag your image with your Docker Hub username before pushing. If your username is johndoe, run docker tag myapp:1.0 johndoe/myapp:1.0 Then push it with docker push johndoe/myapp:1.0 Docker uploads the image to Docker Hub, and anyone can now pull it with docker run johndoe/myapp:1.0

If you want to keep your image private, Docker Hub offers private repositories on paid plans, or you can use other registries like Amazon ECR, Google Container Registry, or Azure Container Registry. Each has its own login and push process, but the steps are similar: log in, tag with the registry URL, and push.

Common mistakes and how to fix them

Forgetting to copy your application code into the image is a frequent mistake — the image builds fine but the container has nothing to run. Check that your COPY instructions actually copy the files you need.

Running commands as root inside the container can be a security risk in production. Add a USER instruction to create a non-root user and switch to it before running your application. For example, add RUN useradd -m appuser and then USER appuser before your CMD instruction.

Hardcoding secrets like database passwords or API keys into your Dockerfile is dangerous because anyone with access to the image can read them. Use environment variables or secret management tools instead, and pass secrets to the container at runtime with -e or --secret.

Frequently Asked Questions

What is the difference between a Dockerfile and a Docker image?

A Dockerfile is a text file with instructions. A Docker image is the compiled result of running those instructions — it is a snapshot of your application and everything it needs to run. Think of the Dockerfile as a recipe and the image as the finished dish.

Do I have to use a base image or can I build from scratch?

You can use FROM scratch to build an image with nothing in it, but this is rare and difficult. Most applications need an operating system, a language runtime, and common tools. Starting from an existing base image saves you from building all of that yourself and is the standard approach.

How do I update my image after I have already built it?

Edit your Dockerfile, then run docker build again with the same tag. Docker rebuilds the image using the new instructions. If you want to keep the old version, tag it with a different version number before rebuilding — for example, rebuild as myapp:1.1 while keeping myapp:1.0 around.

Can I run multiple containers from the same image?

Yes. Each time you run docker run, Docker creates a new container from the image. Multiple containers from the same image run independently and do not interfere with each other. This is one of the main benefits of Docker — one image, many running instances.

What does the dot mean at the end of docker build?

The dot is the build context — it tells Docker which directory to use as the root for COPY and ADD instructions. docker build -t myapp:1.0 . means use the current directory as the build context. You can also point to a different directory: docker build -t myapp:1.0 /path/to/project