Ctrl + K
Docker17 min read

Docker Explained

Understand how Docker containers and images work, learn the basics of Dockerfiles, volumes and networks, and discover common Docker workflows and best practices.

Published: 2026-09-02

Docker is a platform for building, packaging and running applications in isolated environments called containers. Instead of installing an application's dependencies directly on a machine, developers can package the application together with its runtime environment and run the resulting container consistently across different systems.

Docker has become a common part of modern development workflows because it simplifies application setup, testing, deployment and collaboration. A developer can use the same container image locally, in a CI pipeline and on a production server, reducing differences between environments.

What Is Docker?

Docker is a containerization platform that uses operating system features to isolate application processes while allowing them to share the host operating system kernel. A Docker container contains an application and the files, libraries and runtime components required to run it.

Unlike a traditional virtual machine, a container does not normally include an entire guest operating system. This makes containers lightweight and allows many of them to run on the same host with relatively little overhead.

Why Use Docker?

  • Create reproducible development environments.
  • Package applications with their dependencies.
  • Run services in isolated containers.
  • Simplify deployment between environments.
  • Make CI and testing environments more consistent.
  • Run multiple application versions on the same host.
  • Simplify local development for multi-service applications.

The Basic Docker Workflow

A typical Docker workflow starts with application source code and a Dockerfile. Docker uses the Dockerfile to build an image. A container is then created from that image and started as a running process.

Application source code
        ↓
Dockerfile
        ↓
Docker image
        ↓
Docker container
        ↓
Running application

Docker Images

A Docker image is a read-only package containing the filesystem, application files and configuration needed to create a container. Images are built from layers, which allows Docker to reuse unchanged parts between builds.

Images can be created locally, shared through private registries or downloaded from public registries such as Docker Hub. A single image can be used to create multiple containers.

docker pull nginx
docker images

Docker Containers

A container is a running or stopped instance created from a Docker image. The image provides the initial filesystem and configuration, while the container represents a specific runtime instance of that image.

Multiple containers can be created from the same image. Each container has its own isolated process environment, networking configuration and writable container layer.

docker run -d --name web nginx
docker ps
docker stop web
docker start web

Images vs Containers

FeatureImageContainer
PurposeTemplate for an application environmentRuntime instance
StateRead-only layersHas a writable runtime layer
Created byDocker build or image registryDocker run or container creation
Can run?NoYes
Reusable?YesCan be recreated from an image

Dockerfile

A Dockerfile is a text file containing instructions used to build a Docker image. It describes the base image, application files, dependencies, commands and default process that should run inside the container.

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Common Dockerfile Instructions

InstructionPurpose
FROMSelects the base image
WORKDIRSets the working directory
COPYCopies files into the image
ADDAdds files or other supported sources
RUNExecutes a command during image creation
ENVDefines environment variables
EXPOSEDocuments a container port
CMDDefines the default command
ENTRYPOINTDefines the main executable
USERSelects the user used to run processes

Building an Image

The docker build command reads a Dockerfile and creates an image from its instructions. The build context contains the files Docker is allowed to access during the build.

docker build -t my-app:1.0 .

The -t option assigns a repository name and optional tag to the resulting image. The final dot specifies the current directory as the build context.

Running a Container

Once an image exists, docker run can create a container from it. Options can configure the container name, ports, environment variables, volumes and other runtime settings.

docker run -d   --name my-app   -p 3000:3000   my-app:1.0

The -d option runs the container in the background, while -p maps a host port to a port inside the container. Port mapping allows applications inside containers to be reached from outside the container.

Container Ports

Containers have their own network namespace, so a service listening on a container port is not automatically exposed on the host. Docker port publishing creates a connection between a host port and a container port.

docker run -p 8080:80 nginx

In this example, requests sent to port 8080 on the host are forwarded to port 80 inside the Nginx container.

ValueMeaning
8080Host port
80Container port

Container Logs

Docker captures the standard output and standard error streams of containers. The docker logs command can be used to inspect application output, which is particularly useful when debugging containers running in the background.

docker logs my-app
docker logs -f my-app
💡 Design containerized applications to write useful logs to standard output and standard error so Docker and external logging systems can collect them easily.

Executing Commands Inside Containers

The docker exec command runs a command inside an already running container. It is useful for debugging, inspecting files and performing administrative tasks during development.

docker exec -it my-app sh

The -i option keeps standard input available and -t allocates a terminal. The exact shell available inside a container depends on the image. Minimal images may provide sh without including bash.

Docker Volumes

Container filesystems are generally tied to the container lifecycle. If important data is stored only inside a container's writable layer, removing the container can cause that data to be lost. Docker volumes provide persistent storage that exists independently of individual containers.

docker volume create app-data

docker run -d   --name database   -v app-data:/var/lib/data   my-database
Storage TypeTypical Use
Container writable layerTemporary runtime data
Named volumePersistent application data
Bind mountSharing host files with a container

Bind Mounts

A bind mount maps a directory or file from the host machine directly into a container. Bind mounts are particularly useful during development because source code can be edited on the host while the container accesses the updated files.

docker run --rm   -v "$(pwd):/app"   node:22-alpine
⚠️ Be careful when using bind mounts because a container can gain access to files from the host filesystem depending on how the mount is configured.

Docker Networks

Docker networks allow containers to communicate with each other while remaining isolated from unrelated containers. User-defined bridge networks are commonly used to connect application services such as web servers, APIs and databases.

docker network create app-network

docker run -d   --name database   --network app-network   postgres

docker run -d   --name api   --network app-network   my-api

Containers connected to the same user-defined network can generally communicate using container names as DNS names. This makes service-to-service communication easier to configure than relying on changing container IP addresses.

Environment Variables

Environment variables are commonly used to provide configuration to containers without hard-coding environment-specific values into the image. Database URLs, application modes, service endpoints and other configuration values can be supplied when the container starts.

docker run -d   -e NODE_ENV=production   -e API_URL=https://api.example.com   my-app

Environment variables are convenient for configuration, but they should not automatically be treated as a secure secret-management system. Sensitive credentials may require dedicated secret-management solutions depending on the deployment environment.

The .dockerignore File

The .dockerignore file specifies files and directories that should not be included in the Docker build context. Excluding unnecessary files can make builds faster and prevent sensitive or irrelevant data from being sent to the Docker builder.

node_modules/
.git/
.next/
dist/
.env
*.log
💡 Keep .dockerignore updated alongside your project structure. Excluding large directories such as dependencies and build output can significantly reduce the amount of data sent during image builds.

Docker Image Layers

Docker images are composed of layers. Many Dockerfile instructions create filesystem changes that become part of the resulting image. Docker can reuse unchanged layers during later builds, which makes repeated builds faster.

Base image
    ↓
Install dependencies
    ↓
Copy application files
    ↓
Configure runtime
    ↓
Final image

The order of Dockerfile instructions therefore matters for build performance. Instructions that change frequently are often placed later, while relatively stable dependency installation steps can be placed earlier so their layers remain cacheable.

Docker Image Tags

Tags provide human-readable references for image versions. A project can publish multiple tags for different versions or environments, although teams should avoid relying on mutable tags when they require strict reproducibility.

docker build -t my-app:1.4.0 .
docker build -t my-app:latest .

A version-specific tag such as 1.4.0 clearly identifies a particular release, while latest is commonly used as a moving reference to the most recently published image associated with that tag.

Docker Registries

A container registry stores and distributes Docker images. Developers can push images to a registry and deployment systems can pull those images when starting application containers.

docker login

docker tag my-app:1.0 registry.example.com/my-app:1.0

docker push registry.example.com/my-app:1.0
OperationPurpose
docker pullDownload an image
docker buildBuild an image
docker tagAssign an image reference
docker pushUpload an image
docker imagesList local images

Docker Compose

Docker Compose is used to define and run multi-container applications. Instead of manually starting each service with a long docker run command, a Compose file can describe the application's services, networks, volumes, environment variables and dependencies.

services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - database

  database:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: example

With Compose, an application consisting of an API, database and frontend can be started from a single configuration file. This is especially useful for local development and testing.

docker compose up -d
docker compose ps
docker compose logs
docker compose down

Docker Compose Services

ServicePossible Responsibility
webFrontend or web server
apiBackend application
databasePersistent database
redisCache or message broker
workerBackground processing

Docker and Virtual Machines

Docker containers and virtual machines both provide isolation, but they operate at different levels. A virtual machine typically includes a complete guest operating system, while containers share the host kernel and isolate application processes.

AspectContainersVirtual Machines
Guest OSUsually not includedIncluded
Startup timeUsually fastUsually slower
Resource overheadLowerHigher
IsolationProcess-levelHardware virtualization
Typical unitApplication or serviceComplete machine environment

Docker Is Not a Virtual Machine

Although containers can provide strong isolation, they are not miniature virtual machines. Containers share the host kernel, which is one reason they can start quickly and consume fewer resources than full virtual machines.

Docker in Development

Docker can simplify development by standardizing dependencies and services. Instead of asking every developer to install a specific database version, cache server or runtime manually, a project can define those dependencies as containers.

  • Run databases locally without native installation.
  • Use consistent runtime versions.
  • Reproduce project environments quickly.
  • Test multiple service configurations.
  • Reduce setup differences between developers.

Docker in CI/CD

Docker is commonly used in continuous integration and deployment pipelines. A CI system can build an image, run tests inside containers and publish the resulting image to a registry. Deployment systems can then use that exact image as the application artifact.

Source code
    ↓
Build Docker image
    ↓
Run automated tests
    ↓
Push image to registry
    ↓
Deploy image
    ↓
Run containers

Multi-Stage Docker Builds

Multi-stage builds allow a Dockerfile to use multiple FROM instructions and copy only the required output from an earlier build stage into the final image. This is useful for applications that require compilers, package managers or development dependencies during the build but do not need them at runtime.

FROM node:22-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:22-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

RUN npm ci --omit=dev

CMD ["node", "dist/server.js"]

The final image can therefore contain only the runtime files and dependencies needed by the application instead of the entire build environment.

Running as a Non-Root User

Processes inside containers should run as a non-root user whenever possible. Running applications as a non-root user reduces the potential impact of vulnerabilities that allow an attacker to execute commands inside a container.

FROM node:22-alpine

WORKDIR /app

COPY --chown=node:node . .

USER node

CMD ["node", "server.js"]
⚠️ Running a container as root is not automatically a vulnerability, but using a non-root application user is generally a safer default when the application does not require elevated privileges.

Docker Resource Limits

Containers can be configured with resource limits such as CPU and memory constraints. Limits help prevent a single service from consuming excessive host resources and affecting other workloads.

docker run -d   --memory=512m   --cpus=1   my-app

Common Docker Commands

CommandPurpose
docker psList running containers
docker ps -aList all containers
docker imagesList local images
docker pullDownload an image
docker buildBuild an image
docker runCreate and start a container
docker stopStop a container
docker startStart a stopped container
docker rmRemove a container
docker rmiRemove an image
docker logsView container logs
docker execExecute a command inside a container

Common Docker Mistakes

  • Using unnecessarily large base images.
  • Copying the entire project into the build context.
  • Ignoring node_modules, build output or .git directories.
  • Storing important persistent data only inside containers.
  • Running applications as root without a reason.
  • Hard-coding secrets into Dockerfiles.
  • Using mutable image tags when reproducibility is required.
  • Creating containers without health or monitoring considerations.
  • Installing development dependencies into production images.
  • Keeping stopped and unused containers and images indefinitely.

Docker Best Practices

  • Use small and appropriate base images.
  • Create a useful .dockerignore file.
  • Order Dockerfile instructions to take advantage of build caching.
  • Use multi-stage builds when they reduce the final image size.
  • Run applications as a non-root user when possible.
  • Keep secrets outside Dockerfiles and container images.
  • Use explicit image versions for reproducible deployments.
  • Store persistent data in volumes or external storage.
  • Keep containers focused on a single primary responsibility.
  • Make container logs available through standard output and error.
  • Regularly update base images and dependencies.
  • Remove unnecessary packages and build tools from production images.
💡 Treat Docker images as immutable application artifacts. Build a specific image, test it and promote the same image through environments instead of rebuilding different images for development, staging and production.

Docker Security Considerations

Containerization does not automatically make an application secure. Docker security depends on the container image, application, host system, runtime configuration, network exposure and credentials used by the application.

RiskRecommended Protection
Vulnerable base imageRegularly update and scan images
Exposed secretsUse dedicated secret management
Root processesUse non-root users
Unnecessary servicesKeep images minimal
Excessive permissionsUse least privilege
Untrusted imagesUse trusted and verified sources

Docker and Persistent Data

Containers are often treated as replaceable application instances. Persistent state should therefore usually live outside the container's writable layer. Databases, uploaded files and other important data can be stored using Docker volumes or external storage systems.

⚠️ Do not assume that data stored inside a container will survive container replacement. If data matters, store it in an appropriate persistent storage system.

Docker for Local Databases

One of Docker's practical benefits for developers is the ability to run databases without installing them directly on the host operating system. A project can define a database image, configure its environment and attach persistent storage.

docker run -d   --name postgres-dev   -e POSTGRES_PASSWORD=example   -p 5432:5432   postgres:17

This approach makes it easier to create disposable development databases and recreate environments when necessary. For important data, however, appropriate persistent storage and backup procedures are still required.

Docker Lifecycle

A useful way to understand Docker is to separate the lifecycle of an image from the lifecycle of a container. Images are built and distributed, while containers are created, started, stopped and removed as runtime instances.

Dockerfile
    ↓
Build
    ↓
Image
    ↓
Create
    ↓
Container
    ↓
Start
    ↓
Running
    ↓
Stop / Remove

Frequently Asked Questions

What is Docker used for?

Docker is used to package and run applications in isolated containers. It helps create reproducible environments, simplify deployment, run development dependencies and standardize application environments across machines.

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

A Docker image is a read-only package used as a template, while a container is a runtime instance created from that image.

Is Docker a virtual machine?

No. Containers normally share the host operating system kernel, while virtual machines include their own guest operating system.

What is a Dockerfile?

A Dockerfile is a text file containing instructions that Docker uses to build an image. It defines things such as the base image, dependencies, application files and startup command.

Why is .dockerignore important?

The .dockerignore file prevents unnecessary or sensitive files from being included in the Docker build context. This can reduce build time, image-related overhead and accidental exposure of local files.

What are Docker volumes used for?

Docker volumes provide persistent storage that exists independently of individual containers. They are commonly used for databases and other application data that must survive container replacement.

What is Docker Compose?

Docker Compose is a tool for defining and running applications made up of multiple containers. A Compose file can describe services, networks, volumes, ports and environment variables.

Can multiple containers use the same Docker image?

Yes. A Docker image can be used to create many independent containers, with each container having its own runtime state and configuration.

Should Docker containers run as root?

Applications should generally run as a non-root user when they do not require elevated privileges. This follows the principle of least privilege and can reduce the impact of certain security vulnerabilities.

Are Docker containers persistent?

Containers themselves can be stopped and restarted, but their writable filesystem should not normally be treated as durable application storage. Important data should be stored in volumes or external storage.

Helpful Docker Tools

A Dockerfile Generator helps create Dockerfiles for common application stacks, a Docker Compose Generator creates multi-service Compose configurations, a Docker Compose Formatter formats and organizes Compose files, a Docker Ignore Generator creates .dockerignore patterns for common projects, and an Environment Variable Generator helps prepare environment configuration for containerized applications.

Conclusion

Docker provides a practical way to package and run applications in isolated, reproducible environments. Understanding the relationship between Dockerfiles, images and containers is the foundation for using Docker effectively. Volumes provide persistent storage, networks allow services to communicate, environment variables provide runtime configuration and Docker Compose simplifies multi-container applications. By using small images, minimizing privileges, managing secrets correctly and treating containers as replaceable application instances, developers can build more consistent and maintainable development and deployment workflows.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.