MK
Manjit KumarFull Stack Developer
Contact
DevOps

Docker Explained: How Containers Actually Work

17 October 20267 min read7 views
Docker Explained: How Containers Actually Work
#docker#devops#containers

Docker Explained: How Containers Actually Work

A common problem in software development is that an application works perfectly on one machine but fails on another.

The code might be correct. The logic might be correct. Yet the application refuses to start because the runtime version is different, a dependency is missing, or the environment is configured incorrectly.

Docker helps solve this problem by packaging applications and their dependencies into portable containers.

But Docker is more than a way to run applications. Understanding how images, containers, networking, and storage work together is an important step toward understanding modern application deployment.

Let's break it down.

1. What Is Docker?

Docker is a platform for building, distributing, and running applications in containers.

A container provides an isolated environment for an application while sharing the host operating system's kernel.

Imagine you're building a Node.js backend. It needs:

  • A compatible Node.js runtime
  • Installed npm packages
  • Application source code
  • Configuration and environment variables

Without a consistent environment, installing and maintaining these dependencies separately on every machine can become difficult.

Docker allows you to define much of this environment in a repeatable way.

The goal is simple: make running your application more predictable across development, testing, and deployment.

2. Containers vs. Virtual Machines

To understand Docker, first understand what makes containers different from virtual machines.

A virtual machine runs its own guest operating system on virtualized hardware. A container isolates application processes while sharing the host kernel.

Think of it this way:

  • Virtual machine: A separate computer running inside your computer.
  • Container: An isolated application environment running on a shared operating system kernel.

Containers generally start quickly and use fewer resources than full virtual machines. However, their isolation model differs, and they are not a universal replacement for VMs.

Both remain useful in modern infrastructure.

3. The Four Building Blocks of Docker

Dockerfile

A Dockerfile contains the instructions used to build an image.

For example:

dockerfile
FROM node:22-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . EXPOSE 3000 CMD ["npm", "start"]

Here's what each instruction does:

  • FROM selects the base image.
  • WORKDIR sets the working directory.
  • COPY copies files into the image.
  • RUN executes a command during the build.
  • EXPOSE documents the port the application is expected to use.
  • CMD defines the default command used to start the container.

This example assumes a compatible lockfile and a start script in package.json.

Docker Image

An image is an immutable, versioned template used to create containers.

It contains the filesystem layers and configuration required by the application.

You can build an image once and distribute it through a container registry.

Docker Container

A container is an instance of an image.

You can create multiple containers from the same image, with each instance having its own process lifecycle and writable container layer.

Docker Registry

A registry stores and distributes images.

Docker Hub is a popular example. Organizations can also use private registries to manage their application images.

4. From Source Code to a Running Container

The typical workflow looks like this:

text
Application Source Code | v Dockerfile | v Docker Image | v Docker Container | v Running Application

First, you define how the application should be packaged.

Next, Docker builds an image using those instructions.

Finally, you create a container from that image and run the application inside it.

In a production workflow, the image is commonly pushed to a registry and then pulled onto the target server.

5. Run Your First Docker Container

Once Docker is installed, verify that the CLI is available:

bash
docker --version

Run the official test image:

bash
docker run hello-world

Docker downloads the image if it isn't available locally, creates a container, and runs the test program.

Now inspect your containers:

bash
docker ps docker ps -a

The first command lists running containers. The second also includes stopped containers.

List the images stored on your machine:

bash
docker images

These commands help you understand the difference between images and containers in practice.

6. Understanding Docker Networking

Containers often need to communicate with other containers or services outside Docker.

For example, a backend API might need to connect to a database.

Docker provides networking features that let containers communicate through configured networks.

With Docker Compose, you can define a backend and database as separate services on the same network.

The backend can then connect to the database using its service name as the hostname.

One important detail: EXPOSE 3000 in a Dockerfile does not publish port 3000 to your host machine.

To publish a port, you can use:

bash
docker run -p 3000:3000 my-api

This maps port 3000 on the host to port 3000 in the container.

For production systems, exposed ports and network access should be configured deliberately rather than opened unnecessarily.

7. What About Persistent Data?

Containers are replaceable. Their writable filesystem layer should not be treated as permanent storage.

Suppose your application writes important files inside a container. If that container is removed and replaced, those files may be lost.

Docker volumes provide a way to persist data independently of a container's lifecycle.

This is especially important for databases and applications that generate files.

For production workloads, you should also consider backups, access controls, and recovery procedures. A volume by itself is not a backup strategy.

8. Running Multiple Services with Docker Compose

Real applications rarely consist of a single process.

A typical stack might include:

  • A backend API
  • A database
  • A cache
  • A background worker

Managing each container manually becomes inconvenient as the stack grows.

Docker Compose lets you describe multiple services in a YAML configuration file and manage them together.

For example:

yaml
services: api: build: . ports: - "3000:3000" environment: DATABASE_URL: postgres://app:example@db:5432/app depends_on: - db db: image: postgres:17 environment: POSTGRES_USER: app POSTGRES_PASSWORD: example POSTGRES_DB: app volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:

This example illustrates service wiring and persistent storage. Before using a configuration like this beyond local development, use appropriate secrets management, secure database credentials, health checks, and production-specific configuration.

Start the services with:

bash
docker compose up -d

View their status:

bash
docker compose ps

View logs:

bash
docker compose logs -f

Compose is particularly useful for local development, testing, and running smaller multi-container deployments.

9. Common Mistakes to Avoid

Baking secrets into images

Never hardcode production passwords, private keys, or API tokens into an image.

Use environment-specific configuration and a suitable secrets-management solution.

Ignoring image size

Large images take longer to transfer and can increase storage costs.

Use appropriate base images, a .dockerignore file, and multi-stage builds when they provide a benefit.

Treating containers as permanent servers

Containers can stop, restart, or be replaced. Applications should handle shutdowns and failures gracefully.

Forgetting security

Use trusted base images, update dependencies, limit privileges, and avoid running applications as root when practical.

Assuming Docker solves every deployment problem

Docker standardizes application packaging, but it doesn't automatically provide monitoring, high availability, backups, secure configuration, or zero-downtime deployments.

Those require additional engineering decisions.

10. Where Docker Fits in Cloud-Native Development

Docker is an important part of the cloud-native ecosystem, but it is not the entire ecosystem.

A common progression looks like this:

  1. Learn Linux, processes, and networking.
  2. Package applications with Docker.
  3. Run multi-service applications with Docker Compose.
  4. Deploy containers to cloud infrastructure.
  5. Add CI/CD, monitoring, and reliable release practices.
  6. Learn orchestration platforms such as Kubernetes when the problem requires them.

Each step builds on the previous one.

Understanding the fundamentals makes it easier to debug production issues instead of relying on commands without knowing what they do.

Final Thoughts

Docker makes software delivery more repeatable by separating application packaging from the environment in which the application runs.

The most important concepts to understand are images, containers, Dockerfiles, networking, and persistent storage.

Once these ideas become clear, deploying backend services and managing multi-container applications becomes much easier to reason about.

The real value of Docker isn't just running an application in a container. It's being able to package, distribute, and run that application through a predictable workflow.

Share

© 2026 Manjit Kumar - All rights reserved

Last updated

With Love, By Manjit.

;