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 networking
  • overlay – Multi-host distributed networking
  • macvlan – Direct integration with physical networks
  • host – 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:

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:

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!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *