Deploying Docker on a modern Linux VPS (Ubuntu or Debian) requires moving beyond standard repository packages. In production, unconstrained container logging, default daemon settings, and improper package channels frequently lead to disk exhaustion, security vulnerabilities, and service downtime during kernel updates.
This runbook outlines the production standard for installing the official Docker Engine, configuring critical daemon-level safeguards in /etc/docker/daemon.json, enabling non-disruptive daemon updates with live-restore, and establishing a structured GitOps directory architecture.
1. Official Docker CE Installation vs. Distro Packages
Many Linux distributions package Docker as docker.io via standard distribution repositories. In a production environment, avoid docker.io:
- Lagging Versions: Distro packages often lag several minor releases behind upstream security patches and contain outdated Buildx and Compose plugins.
- Broken Compose Integration: Distro packages often ship obsolete Python-based
docker-compose(v1) instead of the integrated Go-baseddocker compose(v2) plugin.
Automated Installation via Official Docker Repository
# Remove any lingering distro packages
sudo apt-get remove -y docker docker-engine docker.io containerd runc
# Install required prerequisites
sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Set up the stable official repository
echo "deb [arch= signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine, containerd, and Compose v2
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
2. Production daemon.json Configuration
By default, Docker ships with no log rotation limits and stops all containers whenever the Docker daemon service restarts. On a VPS with 25GB to 80GB of storage, runaway container logs (e.g. database debug loops or API healthcheck spam) can completely fill the root filesystem within hours, causing kernel panics.
Create or update /etc/docker/daemon.json with the following production defaults:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"no-new-privileges": true,
"userland-proxy": false,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65535,
"Soft": 65535
}
}
}
EOF
Key Architectural Directives Explained
log-driver: json-filewithmax-size: 10m&max-file: 3: Restricts every container to a maximum of 30MB of logs (three 10MB rotated files). Older logs are automatically purged without manual intervention.live-restore: true: Keeps active containers running uninterrupted even when the Docker daemon restarts or whenunattended-upgradespatches thedocker-cepackage.no-new-privileges: true: Security baseline that prevents container processes from acquiring additional privileges viasetuidorsetgidbinaries.userland-proxy: false: Disables the resource-heavydocker-proxyprocess, allowing the Linux kernel’s Hairpin NAT to route traffic directly. This reduces memory usage and preserves genuine client IP addresses.
Apply the configuration and restart Docker:
sudo systemctl restart docker
sudo docker info | grep -E "Logging Driver|Live Restore"
3. Host Filesystem & Stack Directory Architecture
Avoid scattering Docker Compose files across arbitrary user home directories. Structure production services inside /opt/komodo/stacks/ with dedicated directory boundaries:
/opt/komodo/stacks/
├── 0.proxy/
│ ├── compose.yaml # Ingress proxy (Caddy / Cloudflare Tunnel)
│ ├── Caddyfile
│ └── .env
├── vaultwarden/
│ ├── compose.yaml
│ └── .env
└── paperless/
├── compose.yaml
└── .env
Permission Isolation
Grant standard deployment users access to Docker without sudo while securing sensitive configuration files:
# Add deployment user to docker group
sudo usermod -aG docker
# Lock down stack directories
sudo mkdir -p /opt/komodo/stacks && sudo chown -R : /opt/komodo/stacks
chmod 700 /opt/komodo/stacks
Automating Docker Installation & daemon.json with Ansible
To provision Docker Engine and enforce daemon-level safeguards across your entire VPS fleet declaratively, use the following idempotent Ansible tasks:
# roles/docker/tasks/main.yml
---
- name: Remove legacy distro packages
ansible.builtin.apt:
name: [docker, docker-engine, docker.io, containerd, runc]
state: absent
- name: Create apt keyrings directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Download official Docker GPG key
ansible.builtin.get_url:
url: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg"
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
- name: Configure official Docker repository (deb822)
ansible.builtin.deb822_repository:
name: docker
types: [deb]
uris: "https://download.docker.com/linux/{{ ansible_distribution | lower }}"
suites: ["{{ ansible_distribution_release }}"]
components: [stable]
signed_by: /etc/apt/keyrings/docker.asc
architectures: ["{{ ansible_architecture | replace('x86_64', 'amd64') | replace('aarch64', 'arm64') }}"]
state: present
- name: Install Docker CE and Compose plugin
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: true
- name: Deploy hardened /etc/docker/daemon.json
ansible.builtin.copy:
content: |
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"no-new-privileges": true,
"userland-proxy": false
}
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
notify: Restart docker
- name: Create external Docker bridge networks
community.docker.docker_network:
name: "{{ item }}"
driver: bridge
state: present
loop:
- web_net
- db_internal
Related Production Runbooks
- Docker + UFW Firewall Security: Preventing Public Exposure & iptables Bypass
- Zero-Downtime Container Healthchecks, Auto-Healing & Safe Update Strategies
- Automated Security Patching & Maintenance Windows with unattended-upgrades
- The 3-2-1 Backup Architecture: Automated Encrypted Restic Backups to B2 & S3