Understanding Docker: From Fundamentals to Practical Usage
Docker emerged to solve the long-standing problem of environment inconsistency between development and operations. For example, a application developed on Windows may behave differently when deployed on Linux. Docker addresses this by packaging applications along with their runtime environments into standardized units called images.
Inspired by shipping containers, Docker enforces strong isolation between applications. Each container runs independently, enabling efficient utilization of server resources.
| Virtual Machine | Docker |
|---|---|
| Full OS (e.g., CentOS) per instance | Minimal base image with only required dependencies (e.g., JDK, MySQL) |
| Requires multiple full VMs | Runs lightweight containers directly |
| Several gigabytes in size | Typically just a few megabytes |
Unlike traditional virtual machines that emulate entire operating systems, Docker containers share the host kernel. They do not include a guest OS or virtualized hardware, making them significantly lighter and faster to start.
Each container is isolated with its own filesystem, process space, and network stack, yet all run atop the same host kernel. This architecture enables:
- Rapid application delivery and deployment via immutable images
- Simplified scaling and updates through container orchestration
- Consistent environments across development, testing, and production
- High-density deployment—hundreds of containers can run on a single physical machine
Core Concepts
- Image: A read-only template containing code, runtime, libraries, and configuration needed to run an application. Multiple containers can be instantiated from one image.
- Container: A runnable instance of an image, which can be started, stopped, or deleted. It behaves like a lightweight Linux system.
- Repository: A storage location for Docker images, either public (Docker Hub) or private.
Architecture
Docker follows a client-server model. The Docker daemon (dockerd) runs on the host and manages containers, images, and networks. Clients communicate with the daemon via REST API, CLI, or sockets.
Essential Commands
System Info
docker --help # General help
docker info # System-wide information
Image Management
docker search nginx # Search public images
docker pull nginx # Download an image
docker images # List local images
Container Lifecycle
docker run -d --name web_server -p 8080:80 nginx
Common flags:
-d: Run in detached mode--name: Assign a custom name-p HOST:CONTAINER: Publish container port to host-it: Interactive terminal mode
Runtime Operations
docker start|stop|restart container_name
docker logs -f --tail 50 container_name
docker exec -it container_name /bin/bash # Open new shell in running container
docker inspect container_name # View detailed metadata
docker cp container_name:/path/file ./local/ # Copy files between container and host
Practical Examples
Nginx Deployment
docker run -d --name my_nginx -p 3344:80 nginx
curl localhost:3344 # Should return Nginx welcome page
Tomcat Deployment
docker run -d --name tomcat_app -p 3355:8080 tomcat
docker exec -it tomcat_app /bin/bash
# Inside container: copy webapps.dist to webapps if empty
Elasticsearch + Kibana
docker run -d \
--name es_node \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms64m -Xmx512m" \
elasticsearch:7.6.2
curl localhost:9200 # Verify cluster health
Portainer (GUI Management)
docker run -d \
-p 8088:9000 \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce
Access at http://localhost:8088.
Image Layering & UnionFS
Docker uses Union File System (UnionFS), which layers filesystem changes. Each instruction in a Dockerfile creates a new read-only layer. At runtime, a writable container layer is added on top.
Base layers (e.g., bootfs, rootfs) are shared across images, reducing redundancy. Modifications are stored in upper layers, enabling efficient image inheritance and caching.
To persist changes from a container into a new image:
docker commit -a "user@example.com" -m "added config" container_id new_image:tag
Data Persistence with Volumes
Containers are ephemeral—data inside is lost when deleted. Volumes solve this by decoupling data from container lifecycle.
Bind Mounts (Host Path)
docker run -v /host/path:/container/path image
Named Volumes
docker run -v my_volume:/app/data image
docker volume inspect my_volume # Shows host path under /var/lib/docker/volumes/
Anonymous Volumes
docker run -v /app/data image # Auto-generated volume name
Volumes enable data sharing between containers and survive container deletion.
Building Custom Images with Dockerfile
A Dockerfile defines image construction steps:
FROM openjdk:11-jre-slim
COPY app.jar /app/
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Build and run:
docker build -t myapp:1.0 .
docker run -d -p 8080:8080 myapp:1.0
Key instructions:
FROM: Base imageRUN: Execute commands during buildCOPY/ADD: Include filesENV: Set environment variablesVOLUME: Declare mount pointsCMD/ENTRYPOINT: Define startup command
Each instruction creates a new layer, cached for faster rebuilds.
Orchestration with Docker Compose
docker-compose.yml defines multi-container applications:
version: '3.8'
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
volumes:
- db_data:/var/lib/mysql
web:
build: ./app
ports:
- "8080:8080"
depends_on:
- db
volumes:
db_data:
Commands:
docker-compose up -d # Start services
docker-compose logs -f # Stream logs
docker-compose down # Stop and remove
Services communicate using service names as hostnames (e.g., jdbc:mysql://db:3306/db).
Private Registry
Run a local registry:
docker run -d -p 5000:5000 --name reg registry:2
Tag and push:
docker tag myapp:1.0 localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0
For insecure registries, configure /etc/docker/daemon.json:
{
"insecure-registries": ["localhost:5000"]
}
Then reload: systemctl reload docker.
Networking
By default, containers connect to the bridge network (docker0), assigned IPs in 172.17.0.0/16. Containers on the same bridge can communicate directly.
Custom Networks
docker network create --driver bridge --subnet 172.20.0.0/16 mynet
docker run --network mynet --name app1 image
docker run --network mynet --name app2 image
Now app1 can resolve app2 by name.
To connect an existing container to a new network:
docker network connect mynet existing_container
Avoid legacy --link; use user-defined networks for DNS-based service discovery.