Docker Containers for Beginners: Images, Networking, Volumes, and Best Practices
Docker containers package an application with the files and runtime dependencies it needs, then run that package in an isolated process environment. This makes a development environment easier to reproduce and gives deployment systems a consistent artifact to start. The model is useful for a first local experiment, a multi-service development stack, or a production workload managed by an orchestrator.
This guide explains the Docker mental model rather than treating the CLI as a list of unrelated commands. You will learn how images, containers, registries, networks, and volumes fit together; how to write a small Dockerfile; and which security and operations decisions matter before a container leaves a laptop.
What Is Docker?
Docker is a platform and set of tools for building, distributing, and running containers. A container image is an immutable package made from filesystem layers and metadata. A container is a running (or stopped) instance created from an image, with its own writable layer, process namespace, network settings, and resource limits.
The simplest lifecycle is:
Dockerfile and source code → image layers → registry or local image store → container process → logs, network connections, and persistent mounts
Docker Engine provides the daemon and API that manage this lifecycle. The Docker CLI is a client of that API. Docker Desktop packages an engine and development tools for desktop operating systems, while Linux users can install Docker Engine using the instructions for their distribution in the official Docker installation guide.
Containers and virtual machines
Both containers and virtual machines isolate workloads, but they isolate different boundaries. A virtual machine includes a guest kernel and a complete guest operating system on a hypervisor. A Linux container normally shares the host kernel while isolating processes, filesystems, users, and network namespaces. That usually means less startup and storage overhead, but it does not make a container equivalent to a separate machine.
| Concern | Virtual machine | Container |
|---|---|---|
| Kernel | Separate guest kernel | Usually shared with the host |
| Package | Full guest operating system and application | Image layers, application, and dependencies |
| Startup | Usually slower | Usually fast |
| Isolation boundary | Hypervisor and guest OS | Kernel namespaces, cgroups, and runtime |
| Portability | Depends on hypervisor and guest image | Depends on image architecture and host kernel support |
| Best fit | Strong machine boundary or different operating systems | Reproducible application processes and service stacks |
The boundary is platform-dependent. Windows containers require Windows-compatible images and hosts; Linux containers require a compatible Linux kernel environment. For a Windows-specific comparison, see our guide to Windows containers and Docker integration.
Why Containers Exist
Software is sensitive to its environment. A language runtime, native library, operating-system package, configuration file, and file permission can all change how an application behaves. Installing those dependencies directly on every developer workstation or server produces drift and makes a release difficult to reproduce.
Containers address this problem by making the application environment an explicit artifact. The same image can be tested in CI and then supplied to a deployment platform. This does not remove the need for configuration management, security review, backups, or monitoring, but it gives those processes a stable unit to inspect and promote.
Containers are particularly useful when you need to:
- run a service without installing its runtime globally;
- create an isolated local database, queue, or browser-testing dependency;
- build a repeatable CI environment;
- deploy several independently versioned services;
- scale stateless processes horizontally; or
- roll back by starting a previously verified image.
An image is not a backup, and a container is not automatically a durable server. Application state, secrets, network policy, and observability still require explicit design.
How Docker Works: Architecture and Lifecycle
Docker has a client-server shape. The CLI or another API client sends a request to the Docker Engine. The engine coordinates the image store, container runtime, storage drivers, and network drivers. On a Linux host, the runtime uses kernel primitives such as namespaces for isolation and cgroups for resource accounting and limits. Our guide to Linux namespaces and container isolation explains the namespace types behind these runtime boundaries, while Linux cgroups and resource management explains how the kernel accounts for and constrains workload resources. On desktop systems, Docker Desktop provides a Linux or Windows environment that matches the selected container mode.
The image-to-process path
- Build context:
docker buildsends a selected directory to the builder. The.dockerignorefile should remove source-control metadata, local dependencies, credentials, and generated files from that context. - Build instructions: The Dockerfile describes filesystem and metadata changes. Each cacheable instruction can produce a layer, and later builds can reuse layers whose inputs have not changed.
- Image reference: A tag such as
example-web:1.0is a human-friendly name. The immutable digest behind a pushed image is the stronger identity to record for production promotion. - Distribution: A registry stores image manifests and layers.
docker pulldownloads only the layers missing from the local image store. - Container creation:
docker runcreates a container from an image, adds a writable layer, configures namespaces and cgroups, attaches networks and mounts, and starts the configured process. - Runtime lifecycle: The container remains alive while its main process runs. When that process exits, the container stops; its filesystem and metadata remain until the container is removed.
The container runtime does not emulate a full operating system for every process. It starts an ordinary process with isolation and resource controls. That explains both the low overhead and the importance of the host kernel and container permissions.
Images, containers, and registries
An image is read-only from the container’s point of view. A running container gets a thin writable layer on top, so writes made there disappear when the container is removed. Multiple containers can share the same image layers without storing a complete copy for each instance.
A registry is an image distribution service, not necessarily Docker Hub. Organizations can use a private registry or a cloud registry, then grant pull and push access according to the release workflow. Prefer trusted, maintained base images and scan or otherwise inspect images before deployment. The Docker image concepts documentation explains the relationship between images, containers, and registries.
The main Docker objects
| Object | Purpose | Typical command |
|---|---|---|
| Image | Immutable application package | docker image ls |
| Container | Runnable instance of an image | docker ps -a |
| Volume | Docker-managed persistent data | docker volume ls |
| Network | Connectivity and name resolution boundary | docker network ls |
| Registry | Remote image storage and distribution | docker pull / docker push |
Components and Variants
Dockerfile instructions
The most common Dockerfile instructions have distinct responsibilities:
FROMselects the base image and starts a build stage.WORKDIRsets the default directory for later instructions and the process.COPYtransfers files from the build context into the image.RUNexecutes a build-time command and records its result in a layer.ENVsets image-level default environment values; use runtime configuration for deploy-specific values.EXPOSEdocuments a listening port but does not publish it to the host.USERselects the process user and should usually be set to a non-root account.CMDsupplies the default command and arguments.ENTRYPOINTdefines the executable when an image should behave like a dedicated application.
The difference between build-time and runtime is important: a secret passed to a RUN command can remain in a layer, while a runtime environment variable can be exposed to processes and diagnostics. Do not put credentials in a Dockerfile or image.
Storage choices
The writable container layer is appropriate for temporary files and caches. Use a named volume for Docker-managed application data, such as a local database directory. Use a bind mount when the host path itself is part of the workflow, such as mounting source code during local development. Use tmpfs when data must be temporary and memory-backed.
For a deeper decision guide covering volumes, bind mounts, and orchestrated storage, see our container storage guide.
Network choices
Docker creates an isolated default bridge network, but user-defined networks are preferable for applications that need predictable service discovery. Containers on the same user-defined network can resolve one another by service name. Publishing a port with -p HOST:CONTAINER creates a host-to-container path; EXPOSE alone does not.
Host networking removes some isolation and behaves differently across platforms. Overlay networks are intended for multi-host systems and are commonly supplied by an orchestrator. Read the Docker networking overview before choosing a driver. Our container networking guide covers service discovery, policies, and debugging in more detail.
For Compose-specific network topology, including project networks, service-name DNS, and frontend/backend segmentation, see the Docker Compose networking guide.
Single containers and Compose applications
A single docker run is useful for a small service or a quick test. A multi-service application is easier to reproduce with Docker Compose, where services, networks, volumes, health checks, and environment references are declared together. Modern Compose uses the Compose Specification, so a top-level version field is usually unnecessary.
The Docker Compose guide explains how to extend this workflow for local development.
Real-World Use Cases
Local development
A team can run a database or message broker in a disposable container while keeping the application source on the host. A named volume preserves local database state, and a user-defined network lets the application connect to db rather than a changing IP address.
Continuous integration
CI jobs can build an image, run unit or integration tests in a clean container, and publish only artifacts that pass. Pinning base images, keeping build context small, and recording image digests make results easier to reproduce.
Service deployment
Stateless web workers and API processes fit containers well because instances can be replaced from the same image. A production platform may add scheduling, rolling updates, service discovery, secrets, and persistent-volume management. Docker is the packaging and runtime foundation; an orchestrator supplies broader fleet management.
Legacy and platform-specific applications
Containers can standardize applications that have awkward runtime dependencies, but they do not magically convert a Linux application to Windows or vice versa. Match the base image, CPU architecture, kernel features, filesystem expectations, and native libraries to the target host. Use WSL on Windows when a Linux development environment is part of a Windows workstation workflow.
Practical Docker Workflow
Install and verify Docker
Install Docker Engine or Docker Desktop using the official platform-specific instructions, then verify the client can reach an engine:
docker version
docker run --rm hello-world
--rm removes the stopped test container. If the command cannot connect to the daemon, check that the engine is running and that the current user has permission to access it. On a desktop installation, also confirm that the selected Linux or Windows container mode matches the image you intend to run.
Build a small image
Create an app.py file:
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b"Hello from a container\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
Then create a Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
RUN useradd --create-home --shell /usr/sbin/nologin appuser
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]
Build and run it:
docker build --tag example-web:1.0 .
docker run --detach --name example-web --publish 127.0.0.1:8000:8000 example-web:1.0
curl http://127.0.0.1:8000
docker logs example-web
docker stop example-web
docker rm example-web
Binding the published port to loopback keeps this demonstration local. In a real deployment, put ingress or a reverse proxy in front of the service and apply an intentional network policy.
Add a .dockerignore file so unnecessary files do not enter the build context:
.git
.env
__pycache__/
node_modules/
*.log
Add a persistent volume
The following example stores application data in a named volume rather than the container’s writable layer:
docker volume create app-data
docker run --detach \
--name app \
--mount type=volume,src=app-data,dst=/var/lib/app \
example-web:1.0
Do not assume a volume is a backup. Back up its contents, test restoration, and document who can access it. A bind mount can be more convenient for development, but it gives the container a direct relationship with a host path and therefore deserves extra permission review.
Run a two-service stack with Compose
Save this as compose.yaml:
services:
web:
build: .
ports:
- "127.0.0.1:8000:8000"
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_DB: example
POSTGRES_USER: example
POSTGRES_PASSWORD: change-me-locally
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U example -d example"]
interval: 5s
timeout: 5s
retries: 5
volumes:
db-data:
Start, inspect, and stop the stack with:
docker compose up --build --detach
docker compose ps
docker compose logs --follow web
docker compose down
The password in this example is only a local demonstration value. Use an ignored environment file, a secrets mechanism, or the deployment platform’s secret store for any non-disposable environment. depends_on with a health check improves startup coordination, but the application should still handle a database that is temporarily unavailable.
Inspect and clean up
Useful diagnostics include:
docker ps
docker image ls
docker inspect example-web
docker stats
docker system df
Remove only resources you have identified as disposable. docker system prune can delete stopped containers, unused networks, and dangling images; adding -a broadens image deletion, and adding volume pruning can destroy data. Prefer explicit cleanup in scripts and confirm volume backups before removing volumes.
Operational and security checklist
- Use a small, maintained base image and rebuild regularly for security updates.
- Pin important dependencies and record image digests during release promotion.
- Run as a non-root user and avoid unnecessary Linux capabilities or privileged mode.
- Keep secrets out of source code, Dockerfiles, image layers, and public command history.
- Copy only the files needed to build the application and use a
.dockerignorefile. - Prefer multi-stage builds so compilers and test tools do not ship in the runtime image.
- Set CPU and memory limits appropriate to the host or orchestrator.
- Send application logs to standard output and collect them centrally.
- Add health checks that test useful application behavior rather than merely checking that a process exists.
- Scan images and dependencies, and use trusted registries with access control.
- Back up persistent data independently from images and containers.
Docker’s image-building best practices and rootless mode documentation provide implementation details for reducing image risk and daemon privileges.
Common Misconceptions
“A container is a lightweight virtual machine”
Usually not. A container normally shares a kernel with its host environment. It isolates an application process, while a VM supplies a separate guest operating system and kernel. Choose a VM or stronger isolation when the security boundary or operating-system requirements demand it.
“The image contains all persistent application data”
No. Image layers are designed to be immutable and distributable. A database’s data should live in a managed volume or external service and have a tested backup and restore process.
“EXPOSE makes a service reachable”
No. EXPOSE is metadata. Use a port publication such as -p 127.0.0.1:8000:8000 for host access, or configure the platform’s service and ingress layer.
“The container is healthy if the process is running”
Not necessarily. A process can be alive while unable to answer requests, reach a dependency, or accept traffic. Health checks should reflect the readiness and liveness semantics the deployment platform needs.
“Containers remove all dependency and security problems”
They reduce environmental drift but do not replace patching, least privilege, vulnerability management, network controls, backups, or application security. The image and the host both remain part of the attack surface.
“Docker Compose is a production orchestrator”
Compose is excellent for defining and running related services on a host, especially during development and testing. Production fleets may require a scheduler, replicas across failure domains, automated rollout policy, centralized secrets, and persistent storage controls supplied by an orchestrator or managed platform.
Related Articles
- Learn how to define multi-service applications in our Docker Compose local development guide.
- Compare bridge networks, service discovery, and troubleshooting in the container networking guide.
- Choose volumes and other persistence models with the container storage guide.
- If you are working on Windows, follow the WSL installation guide or read about Windows containers and Docker integration.
For authoritative reference material, consult the Docker overview, the Docker Engine networking documentation, and the Docker volumes documentation.

