Skip to content

Build Image From Dockerfile (docker build)


You’ve got a Dockerfile now. It sits there, correct and readable, describing exactly how your image should come together — but it’s just a text file. Nothing has actually happened yet. No image exists, docker image ls doesn’t know it, and there’s nothing you could run a container from.

That gap between “I wrote a recipe” and “I have a dish” is what this lesson closes. One command reads your Dockerfile top to bottom, actually executes every instruction in it, and hands you back a real image — the same kind of image you’ve been listing, inspecting, and cleaning up since the very first lesson.

    flowchart LR
    A[Dockerfile] --> C["docker image build"]
    B[Build context] --> C
    C --> D[New image, layer by layer]
  

The Command

docker image build -t mysite:v1 .

That’s the whole thing. No legacy docker build here — we’re staying consistent with the modular docker image family from the earlier lessons, the same way you’d reach for docker image ls or docker image inspect. build just joins that same group.

Run it against the Ubuntu + nginx Dockerfile from the last lesson:

FROM ubuntu:22.04

WORKDIR /var/www/html

RUN apt-get update && apt-get install -y nginx

COPY index.html .

CMD ["nginx", "-g", "daemon off;"]

And you’ll see output like this (simplified for readability):

[+] Building 8.2s (9/9) FINISHED
 => [1/4] FROM ubuntu:22.04
 => [2/4] WORKDIR /var/www/html
 => [3/4] RUN apt-get update && apt-get install -y nginx
 => [4/4] COPY index.html .
 => exporting to image
 => naming to docker.io/library/mysite:v1

Look closely and that output isn’t random — it’s your Dockerfile, instruction by instruction, each one becoming a numbered step. This is the moment the “recipe” framing from the last lesson stops being a metaphor and becomes literally what’s happening on your screen.

Breaking Down What You Just Typed

The -t Flag: Naming Your Image

docker image build -t mysite:v1 .

-t tags the image as it’s built, in the same repository:tag shape you’ve been reading in docker image ls output this whole time.

Skip it, and Docker still builds the image — it just leaves it with an ID and no name — <untagged>, which makes it far harder to refer to later. There’s rarely a good reason to build without one.

IMAGE        ID             DISK USAGE   CONTENT SIZE   EXTRA
<untagged>   bc2aaa9871ab        332MB          106MB

You can tag the same build multiple times in one go if you want more than one name pointing at it:

docker image build -t mysite:v1 -t mysite:latest .

The .: Your Build Context

That trailing dot isn’t punctuation — it’s an argument, and it’s telling Docker where to look. It’s the build context: the folder (and everything inside it) that gets sent to the Docker daemon so COPY instructions have something to pull from.

    flowchart TB
    subgraph Context["Build context ( . )"]
        F[Dockerfile]
        H[index.html]
        O[other project files]
    end
    Context --> Daemon["Docker daemon"]
    Daemon --> Image["Image layers"]
  

This matters more than it looks like it should. If you run the build from the wrong folder, or your index.html lives somewhere the context doesn’t include, COPY simply won’t find it — not because the command was wrong, but because the file was never sent over in the first place.

Note

The build context is sent in full before the build even starts, so a folder full of unrelated large files (old logs, .git history, downloaded assets) slows every build down, whether or not any of it gets copied into the image. A .dockerignore file fixes this by excluding paths from the context entirely — we’ll put it to real use once we get to best practices.

Watching a Build Actually Happen

Each line in the build output corresponds to one instruction, executing in order, exactly as we walked through when writing the Dockerfile:

    sequenceDiagram
    participant D as Dockerfile
    participant B as Build Process
    participant I as Image

    D->>B: FROM ubuntu:22.04
    B->>I: base layer added
    D->>B: WORKDIR /var/www/html
    B->>I: directory layer added
    D->>B: RUN apt-get install nginx
    B->>I: install layer added
    D->>B: COPY index.html .
    B->>I: file layer added
    D->>B: CMD [...]
    B->>I: default command stored
  

Nothing about this should feel unfamiliar — it’s the exact same layer stack you’d see afterward with docker image history mysite:v1. Building an image isn’t a separate concept from what you already learned about layers; it’s just the moment those layers get created instead of just inspected.

Rebuilding: Where Caching Shows Up

Run the same build a second time without changing anything, and it finishes almost instantly:

[+] Building 0.4s (9/9) FINISHED
 => CACHED [3/4] RUN apt-get update && apt-get install -y nginx
 => CACHED [4/4] COPY index.html .

CACHED means Docker recognized that instruction & everything above it hasn’t changed, so it reused the layer instead of redoing the work. This is exactly the ordering payoff from the previous lesson: because RUN apt-get install nginx sits above COPY index.html ., editing your HTML and rebuilding only redoes the cheap COPY step, not the nginx install.

Change the RUN line itself, though, and caching stops there — every instruction from that point down gets executed fresh, since Docker can no longer assume anything below an altered step is still valid.

If you ever want to force a completely clean build, ignoring the cache entirely:

docker image build --no-cache -t mysite:v1 .

You won’t need this often — it exists mainly for troubleshooting a build you don’t trust anymore.

Confirming the Image Exists

Once the build finishes, it’s not a separate kind of thing from any other image — it shows up exactly where you’d expect:

docker image ls
REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
mysite       v1       7a1b2c3d4e5f   3 seconds ago   152MB

And everything from the inspecting-images lesson applies to it unchanged:

docker image history mysite:v1
IMAGE          CREATED         CREATED BY                                SIZE
7a1b2c3d4e5f   3 seconds ago   CMD ["nginx" "-g" "daemon off;"]          0B
<missing>      3 seconds ago   COPY index.html .                        612B
<missing>      3 seconds ago   RUN apt-get install -y nginx              68MB
<missing>      3 seconds ago   WORKDIR /var/www/html                     0B
<missing>      3 seconds ago   FROM ubuntu:22.04                        77MB

That’s your Dockerfile, staring back at you from the other direction — proof that writing and building really were the same layer stack the whole time, just viewed from opposite ends.

Running Container From Built Image

You’ve now got a real, named image sitting on your machine, built entirely from your own Dockerfile. The obvious next question can be:

How do I actually run a container from it and see my page load?

You already know how to run a container:

docker container run mysite:v1

Since CMD for mysite:v1 image is `nginx -g daemon off;, it starts nginx in forground when you run a container.

Get the container id and inspect the ip address:

docker container inspect f060 | grep -i ipaddress

Once you got the container ip address, you can curl or hit in the browser locally. I use curl:

curl 172.17.0.2

And that’s the exact index.html we set up in our previous lesson:

<html>
	<h1>COPY in Dockerfile</h1>
</html>

Wrapping Up

docker image build is the bridge between the Dockerfile you write and the image you actually use — read the file, execute each instruction as a layer, and hand back something docker image ls can see. Everything you already know about layers, ordering, and caching from the last two lessons doesn’t change here; it just becomes visible, one build output line at a time.

Next up, we’ll take this image see it’s history and history of images in details.

Last updated on