Application deployed directly on the host server. A dependency update triggers a conflict. Production goes down for 2 hours. A container would have isolated the problem in 10 seconds. I lived this scenario multiple times before adopting Docker systematically. When I consolidated an environment with 300+ VMs for a public healthcare organization, the recurring issue was library conflicts between legacy services and modern applications on the same machine. Physical separation via VMs was resource-heavy. Logical separation via containers reduced the footprint by 60% and eliminated runtime conflicts. 73% of organizations run containers in production today (CNCF Annual Survey 2025). Containers reduce deploy times by 70% compared to traditional methods (Sysdig 2025). This is an operational guide for sysadmins, not a theoretical manifesto. You will learn to deploy your first production container with zero downtime.
Docker Deployment: Prerequisites and Test Environment
To follow this guide, you need a test or production server running Ubuntu 24.04 or Debian 12. Allocate at least 2GB of RAM and 20GB of disk space. You need root or sudo privileges to execute the installation commands. All instructions target the Docker Engine version 27.x or later, ensuring compatibility with modern Compose specifications.
What Docker Solves and When to Use It
Docker solves software isolation without the overhead of a full VM. A container shares the host kernel but keeps processes, filesystem, and network isolated. Update a library inside a container, and the host remains unaware.
Use Docker when you need to run services with conflicting dependencies (Python 3.12 vs Python 2.7), require fast rollbacks, or want to standardize environments across development and production. Do not use Docker for monolithic applications tightly bound to the host kernel, or for high-performance databases requiring direct physical I/O unless you have storage optimized for containers.
Docker Engine Installation on Ubuntu
Installation requires a single official command. Avoid default distro repositories, as they often contain outdated versions vulnerable to known CVEs.
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Log out and back in to apply permissions. Verify the engine works:
docker --version
docker run hello-world
If the hello-world container runs, the engine is operational.
Images vs Containers: The Critical Difference
The most common mistake is confusing images and containers. The image is the template. The container is the live process. Think of the image as a class in programming and the container as the instantiated object.
An image consists of read-only layers. When you start a container, Docker adds a writable layer on top. Stop and remove the container, and that writable layer vanishes. Modified data is lost.
# Pull the Nginx latest image
docker pull nginx
# Start a container from the image
docker run -d -p 80:80 nginx
The -d flag detaches the process. The -p 80:80 flag maps host port 80 to container port 80. Without this mapping, Nginx responds only inside the isolated container network.
Essential Docker Commands for Sysadmins
A sysadmin‘s day with Docker boils down to a handful of repeated commands. Memorize these.
# List active containers
docker ps
# List all containers, including stopped ones
docker ps -a
# Read container logs
docker logs container_id
# Enter a running container
docker exec -it container_id bash
# Stop a container
docker stop container_id
# Remove a stopped container
docker rm container_id
The docker exec -it container_id bash command is your SSH into the container. Use it for rapid debugging. If the image lacks bash (like Alpine), use sh instead. The docker logs command shows the stdout of the main process. Add -f to follow logs in real time, similar to tail -f.
Building Custom Images with Dockerfile
Pre-built images work for testing. In production, you build custom images with a Dockerfile. A Dockerfile is a sequence of instructions to assemble an image layer by layer.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y nginx
COPY index.html /var/www/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Each instruction creates a layer. FROM defines the base. RUN executes commands. COPY inserts files from the host. EXPOSE documents the port. CMD defines the main container process. Build the image:
docker build -t my-nginx:1.0 .
The -t flag assigns a name:version tag. The . indicates the Dockerfile is in the current directory. Docker caches layers. If you change only index.html, Docker re-executes only the COPY layer and subsequent ones.
Docker Compose for Multi-Container Stacks
When an application requires multiple services, Docker Compose orchestrates everything with a single YAML file. Compose eliminates the need to launch and link containers manually with long, error-prone commands. You define the network, volumes, and environment variables in one place.
version: '3.8'
services:
web:
image: my-nginx:1.0
ports:
- "80:80"
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret
volumes:
db-data:
Run docker compose up -d to start the entire stack. Compose creates an isolated bridge network for your services. The web container contacts the database using the hostname db, not a hardcoded IP address. This DNS resolution happens automatically within the Docker network.
Volumes and Data Persistence
Containers are ephemeral by design. If a container crashes or is recreated, data inside its internal filesystem vanishes. For a database or a stateful application, this is a critical problem. Docker solves this with volumes. A volume is a directory on the host mounted inside the container.
# Create a named volume
docker volume create my-data
# Mount the volume in a container
docker run -d -v my-data:/var/lib/postgresql/data postgres:16
The -v flag maps the my-data volume to the internal PostgreSQL directory. Data written inside /var/lib/postgresql/data persists on the host even if the container is destroyed. For static configurations, mount a host file directly. The :ro flag sets the mount as read-only, preventing the container from altering the file.
Zero-Downtime Updates in Production
The naive approach stops the old container and starts the new one. In production, this causes downtime. The correct method for a sysadmin is blue-green deployment using a reverse proxy.
- Start the updated container on a different port.
- Update the reverse proxy configuration (Nginx, HAProxy) to point to the new port.
- Stop the old container.
# Start new version on port 8081
docker run -d -p 8081:80 --name web-v2 my-nginx:2.0
# Switch reverse proxy traffic to 8081
# ... modify proxy config ...
# Stop and remove old container
docker stop web-v1
docker rm web-v1
If the new version has a bug, rollback in 10 seconds. Reactivate web-v1 and shift traffic back. Clean up dead images to free disk space using docker system prune -a. This command removes stopped containers, unused images, and layer cache. Be cautious, it deletes everything not used by an active container.
Common Errors and Troubleshooting
Container stops immediately after docker run
Check the logs. The main process crashed. docker logs container_id shows the exact error, whether a wrong CMD or a missing dependency. A container stays alive only while its main process runs in the foreground.
Port already in use on host
If docker run -d -p 80:80 nginx fails, a process on the host occupies port 80. Change the host port (-p 8080:80) or stop the conflicting process. Use ss -tulnp | grep :80 to identify the culprit.
Permission denied on volumes
The container process runs as a specific user. If mounting a host directory, the container user needs write permissions. Fix permissions on the host or specify the user in the Dockerfile with the USER directive.
Disk space exhausted
Docker accumulates dead layers and unused images. Monitor space with docker system df. If usage hits 90%, run docker system prune -a.
Conclusions with Operational Takeaways
Docker solves software isolation with minimal overhead. The main benefit for a sysadmin is predictability. If a container runs on your laptop, it runs in production with the exact same behavior.
Three operational takeaways:
- Install Docker using the official script, not distro repos.
- Always use volumes for stateful data. Containers are ephemeral by design.
- Update in production with blue-green deployment, never stop and pray.
The next step is Docker Compose. If you manage more than 3 containers manually, Compose reduces complexity to a single YAML file. For further details, consult the official documentation on docs.docker.com.
Read also: Nginx Reverse Proxy: Complete Configuration Guide 2026