, , ,

Build Your First Container Image With Docker (DevOps for Beginners, Part 8)

Containers and Docker for beginners: how a Dockerfile becomes an image and a running container, containers versus virtual machines, ports, volumes, and layer caching.

12 min read
Before you start
To try the hands-on steps you will need Docker installed, either Docker Desktop or Docker Engine. Everything here is free.
12 min read
Before you start
To try the hands-on steps you will need Docker installed, either Docker Desktop or Docker Engine. Everything here is free.
DevOps for Beginners · Part 8 of 18
The gist
A container packages your app together with everything it needs to run, so it behaves the same on your laptop, on the CI runner, and in production. Docker is the most common tool for building and running containers. You write a small file called a Dockerfile, build it into an image, and run that image as a container. It is the cure for the oldest excuse in software: it works on my machine.

For the college passout or career switcher who has heard that Docker is essential and wants to actually build and run a container, not just nod along when someone says the word.

A developer finishes a feature, it runs perfectly on their machine, and they hand it off. On the next person is laptop it crashes, because they have a different version of the language, a missing library, or a setting nobody wrote down. Multiply that by a team and a production server, and you get the endless it works on my machine argument. Containers end that argument by shipping the machine along with the code.

The whole idea starts with one small text file. Here is a real Dockerfile for a Node app, the recipe that describes how to package it. Read it as a list of steps a fresh machine would follow.

FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

FROM picks a small base image with Node already installed. WORKDIR sets the folder. COPY brings files in. RUN installs dependencies. EXPOSE documents the port. CMD is the command that starts the app when the container runs.

Part 7 built and tested your code on a clean CI runner. Docker is how you make sure the thing that ran there is the exact same thing that runs in production, byte for byte. That is why containers sit at the center of almost every modern pipeline.

Containers are not virtual machines

People often meet containers by comparing them to virtual machines, and the difference is worth getting right because it explains why containers took over. A virtual machine emulates a whole computer, including its own full operating system, on top of your real one. A container does not. It shares the host is operating system kernel and only packages the app and its dependencies. That makes a container start in seconds and weigh tens of megabytes, where a virtual machine takes minutes to boot and weighs gigabytes.

Virtual machinesContainersApp AApp BGuest OSGuest OSHypervisorHost OS and hardwareApp AApp BApp CContainer engineShared host OS and hardwareContainers drop the extra guest operating systems, so they are small and fast.
AspectContainerVirtual machine
StartupSecondsMinutes
SizeTens of MBSeveral GB
Operating systemShares the host kernelFull OS per machine
OverheadLowHigh
What it bootsA single processA whole system

Image and container: blueprint and instance

Two words get confused constantly, so pin them down. An image is the packaged, frozen blueprint you build from a Dockerfile. A container is a running instance of that image. One image can start many containers, the same way one class can create many objects, or one recipe can make many identical cakes. You build an image once and run it as many containers as you need, on any machine that has Docker.

Imagemyapp:1.0container 1container 2container 3build once, run many

Your first image, built and run

With the Dockerfile from the top of this part sitting next to your code, two commands take you from files to a running app. Build turns the Dockerfile into an image. Run starts a container from it.

$ docker build -t myapp:1.0 .
[+] Building 12.4s (10/10) FINISHED
 => [1/5] FROM docker.io/library/node:22-slim
 => [4/5] RUN npm ci --omit=dev
 => naming to docker.io/library/myapp:1.0
$ docker run -d -p 3000:3000 myapp:1.0
7c9f2a1b8e4d
$ docker ps
CONTAINER ID   IMAGE       PORTS                    NAMES
7c9f2a1b8e4d   myapp:1.0   0.0.0.0:3000->3000/tcp   nifty_babbage
$ curl -I localhost:3000
HTTP/1.1 200 OK

The -t myapp:1.0 names and tags the image. The -d runs the container in the background, and -p 3000:3000 publishes the container’s port 3000 to the same port on your machine so you can reach it. That last flag is the one beginners forget.

Worked example: the port you forgot to publish
You run the container without the port flag and then cannot reach it:

$ docker run -d myapp:1.0
$ curl localhost:3000
curl: (7) Failed to connect to localhost port 3000: Connection refused

The app is running fine inside the container, but the container is a sealed box. Nothing reaches its ports from outside unless you publish them. Add -p 3000:3000 and the connection works. The lesson: a container is isolated by default, and you open exactly the doors you need with -p. This same isolation is what makes containers safe to run side by side.
CommandWhat it does
docker build -t name:tag .Build an image from the Dockerfile here
docker run -d -p 80:3000 nameRun a container and publish a port
docker psList running containers
docker logs nameRead a container’s output
docker exec -it name shOpen a shell inside a running container
docker stop nameStop a running container
docker pull / pushGet or share an image via a registry

Data disappears unless you tell it not to

Here is the default that surprises every beginner. A container is disposable. When you stop and remove it, everything written inside it is gone. Start a database in a plain container, add data, remove the container, and the data vanishes with it. This is not a bug. Containers are meant to be replaceable, so anything you want to keep must live outside the container.

The fix is a volume, a folder on the host machine that the container writes to. You attach it with another flag, -v, and now the data survives the container being destroyed and recreated. The rule of thumb is simple: containers hold your running code, volumes hold your data, and you never store anything precious inside a container’s own filesystem. Getting this wrong is behind a lot of lost-data horror stories from people is first month with Docker.

Layer caching: why the order in a Dockerfile matters

Look again at the Dockerfile. It copies package.json and installs dependencies before it copies the rest of the code. That order is deliberate, and it is one of the most useful things to understand about Docker. Each instruction builds a layer, and Docker caches layers. When you rebuild, it reuses every layer up to the first line that changed, then rebuilds from there.

Dependencies change rarely, but your code changes constantly. By copying the dependency file and running the install first, Docker can reuse that expensive install layer on every rebuild where only your code changed, and installs can take minutes. Copy everything at once instead, and every one-line code change throws away the cache and reinstalls all dependencies from scratch. The difference is a build that takes five seconds versus five minutes. Order your Dockerfile from least-changing to most-changing and your builds stay fast.

When a container will not start: logs and exec

Sooner or later you run a container and it does not stay up. The site is unreachable and docker ps shows nothing, because the container already died. This is the most common Docker moment in a real job, and the way out is always the same two commands. First, docker ps -a shows stopped containers too, including the exit code. Then docker logs shows what the app printed before it quit.

$ docker ps -a
CONTAINER ID   IMAGE       STATUS                     NAMES
b2c3d4e5f6a7   myapp:1.0   Exited (1) 3 seconds ago   sad_hopper
$ docker logs sad_hopper
Error: Cannot find module 'express'
    at Function._resolveFilename (node:internal/modules)

The exit code 1 next to Exited tells you the app crashed rather than shut down cleanly, and the log names the cause: a dependency the image does not have, usually because it was missing from the dependency file the Dockerfile installs. The container was built exactly as instructed. The instructions were incomplete. When you need to look inside a container that is still running, docker exec -it name sh drops you into a shell inside it, where the Linux commands from Part 4 all work, so you can list files and check config from the inside.

CreatedRunningExiteddocker runstop or crashdocker ps shows Running. docker ps -a also shows Exited ones.

Master one container before Compose

Beginners often jump straight to Docker Compose and multi-container setups because a tutorial showed a whole stack at once. Skip that at first. If you cannot confidently build one image, run it, publish a port, read its logs, and shell into it, adding orchestration on top just hides the parts you have not learned. The genuine skill is understanding a single container: what an image is, how a container runs, where its data goes, and how it talks to the outside world. Once that is solid, Compose is a small step, and Kubernetes in the next part makes far more sense.

One more honest opinion: use small official base images like the slim variants, not the biggest one you can find. A smaller base means faster builds, smaller downloads, and fewer security holes to worry about. It is the cheapest good habit in the whole topic.

Interview question you will meet
What is the difference between an image and a container? The clean answer: an image is the read-only blueprint you build from a Dockerfile, and a container is a running instance of that image. One image can spawn many containers. Add that images are built and shared through a registry, while containers are the live processes doing the work, and you have shown you understand the lifecycle, not just the words.
Why this matters on day one
Almost every team ships containers now, so on your first week you will build an image, run it locally, and read its logs to debug something. Being able to say the container is running but the app inside it is crashing, here is the log line, or that endpoint is unreachable because the port is not published, marks you as someone who can work with the stack instead of being blocked by it. These are small skills that make you productive immediately, and none of them require Kubernetes.
Try it yourself
Free, about twenty minutes. Install Docker Desktop or Docker Engine, then run a ready-made image with docker run -d -p 8080:80 nginx and open localhost:8080 in a browser to see the nginx welcome page. Then run docker ps to see it, docker logs on its name to read its output, and docker stop to shut it down. How to check you did it right: the browser shows the nginx page while the container runs, and the page stops loading once you stop the container. You just pulled, ran, inspected, and stopped a container without writing any code.
In practice
Always tag your images with a real version like myapp:1.0 rather than relying on the default latest. The word latest is not magic, it is just a name that moves, and two servers pulling latest a week apart can quietly run different builds. Pin a version in your pipeline and in production so you always know exactly which image is running. When something breaks, being able to say production is on 1.4 and staging is on 1.5 turns a guessing game into a five second answer.

Docker questions that come up early

Do containers replace virtual machines?
Not entirely. They solve different problems. Containers are lighter and perfect for packaging apps, while virtual machines give stronger isolation and can run different operating systems. In practice, containers often run inside virtual machines in the cloud, so you use both, at different layers.

What is a registry?
A place to store and share images, like GitHub is for code. Docker Hub is the best known public one, and companies run private registries too. You push an image to a registry and others pull it, which is how the same image reaches the CI runner and the production server.

Is Docker the only container tool?
No, but it is the one to learn first because it is everywhere and its file format became the standard. Podman and containerd are alternatives that use the same images. Learn Docker and the concepts transfer directly, which is the pattern across this whole series.

Why is my image so huge?
Usually because of a large base image or copying files you do not need. Switch to a slim base, add a .dockerignore file to leave out junk, and for compiled languages use a multi-stage build so the final image holds only what runs. A lean image builds faster and has fewer vulnerabilities.

You can now package an app so it runs the same everywhere. Next up is Part 9 on Kubernetes, the system that runs many containers across many machines and keeps them healthy without you watching them by hand.

New here? Start with Part 3 on the DevOps loop and Part 7 on CI/CD, which builds these images automatically. The Cloud for Beginners series pairs well for where containers run.

DevOps for Beginners · Part 8 of 18
« Previous: Part 7  |  Complete Guide  |  Next: Part 9

References

Docker: Get started
Docker: Dockerfile best practices
Docker: Volumes and persistent data

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading