Docker has seen immense growth since its launch in 2013, with over 13 billion downloads and quickly becoming the industry standard for containerization. As surveyed by Statista in 2022, over 65% of organizations now utilize container technology like Docker in their tech stacks:
But what makes Docker so popular? And why run applications in containers rather than virtual machines?
In this comprehensive 3200+ word guide, I‘ll cover:
- Docker vs. VMs: Key differences and advantages
- Step-by-step Docker install on Debian 12
- Configuring Docker security, optimization, and networking
- Docker registries, Dockerfiles, and docker-compose basics
- Docker host administration best practices
Let‘s get started!
Docker Containers vs. Virtual Machines
Before we dive into using Docker, it helps to understand how containerization differs from traditional virtualization.
While VMs run guest machine operating systems on top of a physical host, containers share the host OS kernel and only virtualize the user space. This makes them extremely lightweight and fast compared to VMs.
Some key differences:
Virtual Machines | Docker Containers | |
---|---|---|
OS | Guest OS kernel | Host OS kernel |
Startup time | Slow, cold start | Fast |
Hardware utilization | Entire VM must be provisioned | Share only what they need |
Performance | Hypervisor overhead | Near native |
Portability | Hardware dependence | Run on any platform |
Size | GBs-TBs typically | MBs-GBs |
Because they don‘t reproduce entire OS stacks, containers have major advantages in density and portability over VMs.
But VMs can provide stronger workload isolation by segmenting on kernel boundaries.
In many modern environments, Docker and VMs are used together to realize the best of both worlds! Docker for dense application deployment, VMs for secure multi-tenancy.
Now let‘s get Docker running on Debian…
Step 1 — Install Docker Engine, CLI, and Containerd
With Debian 12 installed, we‘ll install the latest stable Docker from Docker‘s official APT repository:
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
This adds Docker‘s GPG key, configures their apt repo for our Debian version, and installs:
- docker-ce – The Docker Engine which runs containers
- docker-ce-cli – Command line interface for communicating with Engine
- containerd – Container runtime that manages container lifecycles
- docker-compose – Tool for running multi-service applications
Step 2 — Validate Your Docker Installation
Check everything installed correctly by running:
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly...
And verify version info:
$ docker version
Client:
Version: 23.0.1
API version: 1.42
Go version: go1.19.4
Git commit: a5ee18f
Built: Thu Feb 9 19:37:52 2023
OS/Arch: linux/amd64
Context: default
Server:
Engine:
Version: 23.0.1
API version: 1.42 (minimum version 1.12)
Go version: go1.19.4
Git commit: 87a90dc
Built: Thu Feb 9 19:36:34 2023
OS/Arch: linux/amd64
Experimental: false
The Docker client and server are installed and able to communicate correctly.
Step 3 — Manage Docker as a Non-Root User
By default, Docker requires root privileges to run commands because its daemon binds to a Unix socket owned by root.
We can avoid having to use sudo
for Docker by adding our user to the docker
group:
sudo usermod -aG docker $USER
Then log out and back in for changes to apply.
Now you can run docker ps
, docker exec
, etc without sudo!
Warning: This effectively grants the docker group root permissions. Only add trusted admin users.
For enhanced security, add users temporarily instead:
sudo gpasswd -a $USER docker
# User docker now
sudo gpasswd -d $USER docker
# Remove when done
Step 4 — Configure Docker to Start on Reboot
Next we‘ll configure the Docker and containerd services to auto-start on boot so our containers restart anytime the server reboots:
$ sudo systemctl enable docker.service
$ sudo systemctl enable containerd.service
Easy container persistence!
Docker Security Best Practices
Since containers share resources with the host OS, hardening your Docker daemon is critical for infrastructure security.
Follow standards like CIS Docker Benchmarks or guidelines from your infosec team. But some key best practices include:
Update Docker Regularly
$ sudo apt update && sudo apt upgrade docker-ce
Security patches are frequently released. Stay on top of them.
Use Docker Content Trust
Docker Content Trust applies digital signatures to images enabling you to verify integrity and publisher.
Enable it in your Docker config:
$ export DOCKER_CONTENT_TRUST=1
Scan Images for Vulnerabilities
$ docker scan my-image-name
Also configure continuous scanning via Docker Security Scanning.
Lock Down Docker Daemon TCP Socket
Protect Docker‘s daemon socket with firewall rules, certificates, or Unix permissions. For example:
$ sudo chown root /var/run/docker.sock && chmod 0600 /var/run/docker.sock
$ sudo ufw deny 2375
Avoid Privilege Escalation Inside Containers
Drop privileges in containers by specifying users other than root:
USER 1001
Or add the --user
flag: docker run --user=1001
Limit Container Resources
Set memory, CPU shares, kernel capabilities, and PIDs to minimums. For example:
docker run --cpus=.5 -m 100M --cap-drop ALL --pids-limit=50
See Limiting Resources for more examples.
Following security best practices will keep your Docker hosts and containers safe, stable, and production-ready!
Docker Networking and Persistent Storage
Now that we have Docker installed securely, let‘s enable container networking and persistent storage.
Networking
By default, the Docker daemon creates a virtual bridge network called docker0
that containers connect through.
We can create custom networks instead for isolation:
docker network create my-app-net
docker run -d --net my-app-net nginx
Common network drivers include:
bridge
– Single host networkingoverlay
– Multi-host distributed networkingmacvlan
– Direct integration with physical networkshost
– Gain performance by skipping virtualization
For multi-container and multi-network communication, assign containers unique names using --name <name>
:
docker run -d --net=backend --name db mysql
docker run -d --net=frontend --name web nginx
Then link via named container aliases:
docker run --net=frontend --name api -e DB_HOST=db api-app
See Networking in Compose for examples.
Storage
For persistent data, bind mount host directories as data volumes:
docker run -v /mnt/data:/var/lib/mysql mysql
Use read-only volumes for static assets:
docker run -v /cdn:/usr/share/nginx/html:ro nginx
And tmpfs
mounts for non-persistent temp data:
docker run -it --tmpfs /tmp debian bash
See Manage data in containers for more examples.
Now let‘s optimize our Docker installations…
Optimizing Docker Performance
Carefully tuning Docker resources and ratios can provide huge application performance gains.
As surveyed by Orca Security, poor container configuration results in ~250MB memory waste per container on average:
Follow standards like Container Solutions Top 10 Practices when configuring containers. But some key optimization best practices include:
Clean Up Unused Docker Artifacts
Frequently prune unused containers, networks, images, and build cache:
docker system prune [-a] [--volumes --images]
And verify usage:
docker system df [-v]
Limit Container Scope
Avoid running as root whenever possible. Drop privileges inside containers with:
USER 1001
And limit access with --cap-drop=ALL
:
docker run --cap-drop=ALL nginx
Centralize Container Logging
Pipe logs to stdout
/stderr
instead of file mounts. Then aggregate streams externally with Docker logging drivers.
Common drivers include Fluentd, Logstash, Splunk, etc.
Benchmark Resource Usage
Profile application needs before setting resource limits:
docker stats $(docker inspect -f "{{.Name}}" $(docker ps -q))
Then allocate +/- 10-25% buffer. Avoid overprovisioning.
Distribute Large Containers
Take advantage of Docker‘s layered filesystem to minimize size:
COPY react-ui /ui
RUN npm install /ui
COPY python-api /api
RUN pip install -r /api/requirements.txt
Differentiate frequently changing dirs from static ones.
Leverage BuildKit and Multistage Builds
BuildKit speeds up image builds by enabling parallel staging:
DOCKER_BUILDKIT=1 docker build .
Multistage builds minimize size by copying only artifacts:
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package
FROM openjdk:8-jre-slim
COPY --from=build /app/target/*.jar /usr/app/
Following Docker optimization best practices will ensure high density, performant applications!
Docker Registries, Dockerfiles and Docker Compose
When running containers in production, central repositories and standardized definitions are key for scale.
Docker Registries
Docker registries store and distribute images for consistent container deployments anywhere.
Popular managed registries include:
- Docker Hub – Docker‘s public registry
- AWS ECR – Amazon Elastic Container Registry
- Azure Container Registry – Microsoft‘s registry
- Google Container Registry – GCR
For private registries, open source options like Harbor and Quay offer enhanced security and audit controls.
When using registries:
- Namespace images consistently:
registry.domain.com/app/service
- Tag immutable image versions:
api-service:v1.2.3
- Delete outdated images to save space
- Scan all images with tools like Clair or Anchore
- Use RBAC to control access
- Enable geo-replication for redundancy
Dockerfiles
Reproducible Docker images start with standardized Dockerfiles.
Dockerfiles define app envs, configs, deps and allow you to version and iterate images safely.
Best practices include:
- Specify OS, env vars, UID first
- Leverage cached builds with appropriate layering
- Validate builds with healthchecks
- Follow conventions like Container Solutions‘ Dockerfile Frontend
For example:
FROM node:18-alpine As build
ENV NODE_ENV=production
ENV PORT=8080
ENV USER=node
WORKDIR /home/${USER}/app
COPY package*.json ./
RUN npm install
COPY public ./public
COPY src ./src
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /home/node/app/build /usr/share/nginx/html
EXPOSE 8080
HEALTHCHECK CMD curl --fail localhost:8080 || exit 1
CMD ["nginx", "-g", "daemon off;"]
Version controlling Dockerfiles enables reliable CI/CD workflows.
Docker Compose
Lastly, Docker Compose allows defining multi-service apps in simple YAML:
services:
proxy:
image: nginx:alpine
api:
image: myapi:v1.0
environment:
DB_NAME: prod_db
db:
image: postgres:13-alpine
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Then start everything with docker-compose up
!
Compose simplifies networking and manages app dependencies automatically.
Conclusion
In this guide, we walked through:
- Key differences between Docker and traditional VMs
- Installing Docker and containerd on Debian 12
- Configuring networking, storage, security, and system resource optimization
- Docker registries, Dockerfiles, and Compose for simplified deployments
Hopefully you now feel empowered to start architecting your own container infrastructures!
I highly encourage further reading into:
- Docker Swarm/Kubernetes for cluster management
- CI/CD tooling like Jenkins, Spinnaker, ArgoCD, and CodePipeline
- Third party scheduling and runtimes (Mesos, Nomad, Kata Containers)
- Host level container managers (Podman, CRI-O, Containerd standlone)
- Exploring alternatives like LXC, Jetpack Compose, etc
As Gartner predicts, over 70% of organizations will run containerized applications by 2025, so knowledge of these systems is becoming imperative!
Let me know in the comments if you have any other questions as you being your Docker journey!