15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
08.10.2024

Docker on Ubuntu: Complete Installation and Usage Guide

Docker is an open-source containerization platform that packages applications and their dependencies into isolated, portable units called containers. Unlike virtual machines, containers share the host OS kernel, making them significantly lighter, faster to start, and more resource-efficient β€” a critical distinction for anyone running workloads on a VPS Hosting environment where compute resources directly affect cost and performance.

This guide covers the full Docker installation process on Ubuntu 20.04, 22.04, and 24.04 LTS, including post-installation hardening, Docker Compose workflows, and production-relevant command patterns that most tutorials omit.

Prerequisites and System Requirements

Before executing a single command, verify the following:

  • Ubuntu version: 20.04 LTS (Focal), 22.04 LTS (Jammy), or 24.04 LTS (Noble). The `lsb_release -cs` command used during repository setup will automatically detect your codename.
  • Architecture: `amd64`, `arm64`, or `armhf` are all supported by Docker's official repository.
  • Kernel version: Docker requires Linux kernel 3.10 or higher. Run `uname -r` to confirm.
  • User privileges: `sudo` or root access is mandatory for installation and daemon management.
  • Disk space: At minimum 2 GB free on the partition hosting `/var/lib/docker`, which is where Docker stores images, containers, volumes, and build cache. On production systems, mount this directory on a dedicated partition or volume.

Critical pre-installation step: If you previously installed Docker from Ubuntu's default `apt` repository (the `docker.io` package), remove it first to avoid conflicts with the official Docker CE packages:

“`bash

sudo apt remove docker docker-engine docker.io containerd runc

“`

Step 1: Update System Packages

Bring the package index and installed packages to their latest versions before adding any new repository:

“`bash

sudo apt update

sudo apt upgrade -y

“`

This ensures that `apt`'s dependency resolver works against current package metadata and that your base system libraries are not outdated β€” a common source of subtle runtime errors with containerized applications.

Step 2: Install Docker Engine from the Official Repository

Ubuntu's default `apt` repositories ship a package called `docker.io`, which is maintained by Canonical and typically lags several versions behind Docker's official release. For production use, always install from Docker's own repository.

2.1 Install Transport and Verification Dependencies

“`bash

sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release -y

“`

Why `gnupg`? Starting with Ubuntu 22.04, `gpg` is not always present by default. Including it explicitly prevents the GPG key import from failing silently.

2.2 Add Docker's Official GPG Key

“`bash

sudo install -m 0755 -d /usr/share/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg –dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg

“`

The `chmod a+r` step is frequently skipped in tutorials but is necessary on systems where `apt` runs under a restricted user context β€” without it, the package manager cannot read the keyring and will throw a `NO_PUBKEY` error during `apt update`.

2.3 Add the Docker Stable Repository

“`bash

echo

"deb [arch=$(dpkg –print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg]

https://download.docker.com/linux/ubuntu

$(lsb_release -cs) stable" |

sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

“`

The `arch=$(dpkg –print-architecture)` substitution is essential on ARM-based servers. Hardcoding `amd64` here is a common mistake that causes silent package resolution failures on ARM instances.

2.4 Install Docker Engine, CLI, and Plugins

“`bash

sudo apt update

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

“`

Package breakdown:

PackageRole
`docker-ce`Docker Engine daemon (`dockerd`)
`docker-ce-cli`Client CLI (`docker` command)
`containerd.io`Low-level container runtime (OCI-compliant)
`docker-buildx-plugin`Extended build capabilities (multi-platform, BuildKit)
`docker-compose-plugin`Compose V2 integrated as a Docker CLI plugin

Note on `containerd.io`: This is not the same as the `containerd` package in Ubuntu's default repository. Docker's `containerd.io` is a specific, tested version of the containerd runtime. Mixing the two is a known source of daemon startup failures.

Step 3: Verify the Installation

Confirm the daemon is active and enabled to start on boot:

“`bash

sudo systemctl status docker

sudo systemctl enable docker

“`

Check the installed version:

“`bash

sudo docker –version

sudo docker info

“`

`docker info` is more informative than `–version` alone β€” it reveals the storage driver in use (typically `overlay2`), the cgroup driver (`systemd` vs `cgroupfs`), and the number of CPUs and memory Docker can access.

Run the canonical smoke test:

“`bash

sudo docker run hello-world

“`

A successful run prints a "Hello from Docker!" message and confirms that the Docker daemon, image pull from Docker Hub, and container execution are all functioning correctly.

Step 4: Configure Docker for Non-Root Access

By default, the Docker socket (`/var/run/docker.sock`) is owned by `root` and the `docker` group. Any user not in the `docker` group must use `sudo` for every Docker command.

“`bash

sudo usermod -aG docker $USER

“`

Apply the group membership without logging out:

“`bash

newgrp docker

“`

Verify:

“`bash

docker run hello-world

“`

Security warning: Membership in the `docker` group is effectively equivalent to passwordless `sudo`. A user in the `docker` group can trivially mount the host filesystem into a container and escape all filesystem-level access controls. On multi-tenant systems or shared servers, consider using rootless Docker instead:

“`bash

dockerd-rootless-setuptool.sh install

“`

Rootless mode runs the Docker daemon and containers under an unprivileged user namespace, dramatically reducing the attack surface. It is the recommended configuration for any environment where multiple users share the same host.

Step 5: Essential Docker Commands Reference

Image Management

“`bash

Pull a specific image version from Docker Hub

docker pull nginx:1.27-alpine

List locally cached images

docker images

Remove a specific image

docker rmi nginx:1.27-alpine

Remove all dangling (untagged) images to reclaim disk space

docker image prune

Remove all unused images (not just dangling)

docker image prune -a

“`

Production tip: Always pull versioned tags (e.g., `nginx:1.27-alpine`) rather than `latest` in any automated or production workflow. The `latest` tag is mutable β€” it can point to a different image after a registry push, breaking reproducibility.

Container Lifecycle

“`bash

Run a container interactively with a pseudo-TTY

docker run -it ubuntu:22.04 /bin/bash

Run a container in detached mode with port mapping and a name

docker run -d -p 8080:80 –name my-nginx nginx:1.27-alpine

List running containers

docker ps

List all containers (including stopped)

docker ps -a

Stop a running container gracefully (SIGTERM, then SIGKILL after timeout)

docker stop my-nginx

Start a stopped container

docker start my-nginx

Remove a stopped container

docker rm my-nginx

Force-remove a running container (sends SIGKILL immediately)

docker rm -f my-nginx

View real-time logs

docker logs -f my-nginx

Execute a command inside a running container

docker exec -it my-nginx /bin/sh

“`

Resource and System Inspection

“`bash

Display real-time resource usage statistics

docker stats

Inspect detailed container metadata (JSON)

docker inspect my-nginx

Display disk usage by Docker objects

docker system df

Remove all stopped containers, unused networks, dangling images, and build cache

docker system prune

“`

`docker system prune` is one of the most important maintenance commands for long-running servers. Without periodic cleanup, Docker's build cache and stopped containers can consume tens of gigabytes on an active development or CI host.

Step 6: Docker Compose β€” Multi-Container Application Orchestration

Docker Compose V2 (the `docker-compose-plugin` installed earlier) is invoked as `docker compose` (with a space), not the legacy `docker-compose` (with a hyphen). Both syntaxes work if you have the plugin installed, but V2 is the current standard.

6.1 Understanding the Compose File Structure

Create a project directory and a `compose.yml` file (the preferred filename in Compose V2; `docker-compose.yml` remains supported for backward compatibility):

“`bash

mkdir ~/my-web-app && cd ~/my-web-app

nano compose.yml

“`

A production-realistic example with Nginx and a backend service:

“`yaml

services:

web:

image: nginx:1.27-alpine

ports:

  • "8080:80"

volumes:

  • ./nginx.conf:/etc/nginx/nginx.conf:ro
  • ./html:/usr/share/nginx/html:ro

depends_on:

  • app

restart: unless-stopped

app:

image: node:20-alpine

working_dir: /usr/src/app

volumes:

  • ./app:/usr/src/app

command: node server.js

environment:

  • NODE_ENV=production

restart: unless-stopped

“`

Key Compose directives explained:

  • `restart: unless-stopped` β€” The container restarts automatically after a crash or system reboot, unless it was explicitly stopped by the operator. This is the correct policy for long-running services; `always` will restart even intentionally stopped containers.
  • `depends_on` β€” Controls startup order but does not wait for the service to be *ready* (e.g., a database accepting connections). For readiness gating, use `healthcheck` combined with `condition: service_healthy`.
  • `volumes` with `:ro` β€” Mounting configuration files as read-only prevents a compromised container process from modifying its own configuration.

6.2 Compose Workflow Commands

“`bash

Start all services in detached mode

docker compose up -d

View logs for all services

docker compose logs -f

View logs for a specific service

docker compose logs -f web

List running Compose services

docker compose ps

Scale a specific service to multiple replicas

docker compose up -d –scale app=3

Stop services without removing containers

docker compose stop

Stop and remove containers, networks, and volumes

docker compose down –volumes

Rebuild images before starting (useful after code changes)

docker compose up -d –build

“`

6.3 Verify the Running Service

“`bash

curl -I http://localhost:8080

“`

A `200 OK` response confirms Nginx is serving correctly. For services running on a remote VPS Hosting instance, replace `localhost` with your server's public IP address and ensure the relevant port is open in your firewall (`ufw allow 8080/tcp`).

Step 7: Docker Networking Fundamentals

Understanding Docker's networking model is essential for building multi-container applications that communicate securely.

Default network drivers:

DriverUse Case
`bridge`Default for standalone containers; isolated network namespace on the host
`host`Container shares the host's network stack; maximum performance, zero isolation
`none`No network access; useful for batch processing or security-sensitive workloads
`overlay`Multi-host networking for Docker Swarm clusters
`macvlan`Assigns a MAC address to the container; appears as a physical device on the network

Creating and using a custom bridge network:

“`bash

Create an isolated network

docker network create my-app-network

Run containers attached to the custom network

docker run -d –name db –network my-app-network postgres:16-alpine

docker run -d –name api –network my-app-network my-api-image

Containers on the same custom bridge network can resolve each other by name

Inside 'api', you can connect to 'db' using the hostname 'db'

“`

Custom bridge networks provide automatic DNS resolution between containers by container name. The default `bridge` network does not β€” this is a critical distinction that causes connection failures when developers assume containers on the default network can reach each other by name.

Step 8: Persistent Data with Docker Volumes

Containers are ephemeral by design. Any data written inside a container's filesystem is lost when the container is removed. For persistent storage, use volumes or bind mounts.

“`bash

Create a named volume

docker volume create pgdata

Use the volume with a container

docker run -d

–name postgres-db

-e POSTGRES_PASSWORD=securepassword

-v pgdata:/var/lib/postgresql/data

postgres:16-alpine

List volumes

docker volume ls

Inspect a volume (shows mount point on host)

docker volume inspect pgdata

Remove unused volumes

docker volume prune

“`

Volumes vs. bind mounts:

FeatureNamed VolumeBind Mount
Managed by DockerYesNo
Host path requiredNoYes
Portable across hostsYes (with volume drivers)No
Best forDatabase data, application stateDevelopment code, config files
Backup mechanism`docker run –volumes-from`Standard filesystem tools

Step 9: Keeping Docker Updated

Docker's official repository handles updates through the standard `apt` mechanism:

“`bash

sudo apt update

sudo apt upgrade -y

“`

To update only Docker-related packages without upgrading the entire system:

“`bash

sudo apt install –only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

“`

Check the Docker release notes before major version upgrades, particularly for changes to the storage driver, cgroup version handling, or deprecated API versions that may affect existing `compose.yml` files.

Docker vs. Alternative Containerization Approaches

FeatureDocker EnginePodmanLXC/LXDcontainerd (standalone)
Daemon architectureCentralized daemonDaemonlessDaemon-basedDaemon-based
Rootless supportYes (v20+)NativeLimitedYes
Docker Compose supportNativeVia `podman-compose`NoNo
OCI complianceYesYesNo (LXC format)Yes
Kubernetes integrationVia CRI-dockerd shimNative CRINoNative CRI
Windows/macOS supportDocker DesktopLimitedNoNo
Best fitGeneral development and productionSecurity-focused, rootlessSystem containers, VMsKubernetes nodes

For teams running containerized workloads at scale on bare metal, a Dedicated Servers environment gives you full control over kernel parameters, storage I/O scheduling, and network configuration β€” all of which directly affect container density and performance.

Production Hardening Checklist

Before running Docker in a production environment, address the following:

Daemon configuration (`/etc/docker/daemon.json`):

“`json

{

"log-driver": "json-file",

"log-opts": {

"max-size": "10m",

"max-file": "3"

},

"storage-driver": "overlay2",

"userns-remap": "default",

"live-restore": true,

"no-new-privileges": true

}

“`

  • `log-opts` with `max-size` and `max-file`: Without log rotation, Docker's JSON log files will fill your disk. This is one of the most common causes of unexpected server outages on containerized hosts.
  • `userns-remap: "default"`: Enables user namespace remapping, so container root (UID 0) maps to an unprivileged UID on the host.
  • `live-restore: true`: Allows containers to keep running if the Docker daemon crashes or is restarted during an upgrade β€” critical for zero-downtime maintenance.
  • `no-new-privileges: true`: Prevents container processes from gaining additional privileges via `setuid` or `setgid` binaries.

Network and firewall considerations:

Docker manipulates `iptables` directly and bypasses `ufw` rules by default. A container with a published port (`-p 8080:80`) will be accessible from the internet even if `ufw deny 8080` is set. To enforce `ufw` rules over Docker's `iptables` manipulation, add `"iptables": false` to `daemon.json` and manage routing manually, or use Docker's `–network host` with explicit `ufw` rules.

For projects requiring HTTPS termination, pair your containerized application with a properly configured reverse proxy (Nginx or Traefik) and a valid certificate. SSL Certificates are a prerequisite for any production web service running behind a containerized stack.

If your workload involves machine learning inference, model serving, or GPU-accelerated data processing inside containers, the NVIDIA Container Toolkit integrates directly with Docker Engine. GPU Hosting provides the underlying hardware required for these workloads.

For teams managing multiple projects with web-based container management, VPS with cPanel offers a managed control panel environment that can complement Docker-based deployments for simpler application stacks.

Key Technical Takeaways and Decision Matrix

When to use Docker on a VPS vs. a dedicated server:

  • Use a VPS for development environments, staging, and low-to-medium traffic production workloads where container density is 10–50 containers.
  • Use a dedicated server when container density exceeds 50 instances, when you need predictable I/O performance (no noisy-neighbor effect), or when kernel parameter tuning (`sysctl`) is required for your workload.

Operational checklist before going live:

  • Configure log rotation in `daemon.json` (`max-size`, `max-file`)
  • Enable `live-restore` to survive daemon restarts without container downtime
  • Use named volumes, not bind mounts, for stateful service data
  • Pin image versions in all `compose.yml` files β€” never use `latest` in production
  • Enable `userns-remap` or run rootless Docker on multi-tenant hosts
  • Audit `iptables` rules after Docker installation to confirm firewall policy is not bypassed
  • Set `restart: unless-stopped` on all long-running services
  • Run `docker system prune` on a scheduled cron job to prevent disk exhaustion
  • Use custom bridge networks for all inter-container communication β€” never rely on the default bridge for service discovery

FAQ

Does Docker on Ubuntu use `systemd` or `cgroupfs` as the cgroup driver by default?

Since Docker Engine 20.10, the default cgroup driver on `systemd`-based systems (including all modern Ubuntu LTS releases) is `systemd`. This aligns with Kubernetes requirements and avoids the instability caused by running two cgroup managers simultaneously. You can verify with `docker info | grep -i cgroup`.

What is the difference between `docker compose down` and `docker compose stop`?

`docker compose stop` halts running containers but preserves them and their associated networks. `docker compose down` stops containers and then removes them along with the networks Compose created. Adding `–volumes` to `down` also removes named volumes defined in the Compose file β€” use this flag with caution in production, as it permanently deletes persistent data.

Why does Docker bypass `ufw` firewall rules on Ubuntu?

Docker inserts its own `iptables` rules in the `DOCKER` and `DOCKER-USER` chains, which are evaluated before `ufw`'s `INPUT` chain rules. This means a port published with `-p` is reachable from the internet regardless of `ufw` policy. The correct mitigation is to add rules to the `DOCKER-USER` chain directly, or to bind published ports to a specific interface (e.g., `-p 127.0.0.1:8080:80`) when external access is not required.

How do I limit the CPU and memory a Docker container can consume?

Use resource constraint flags at runtime: `docker run –memory="512m" –cpus="1.5" my-image`. In Compose, set these under the `deploy.resources` key (Compose V2) or the top-level `mem_limit` and `cpus` keys. Without limits, a single runaway container can exhaust host resources and bring down all other containers on the same host.

Can I run Docker inside a Docker container (Docker-in-Docker)?

Yes, but it is strongly discouraged for production use. The common pattern for CI pipelines is to mount the host Docker socket (`-v /var/run/docker.sock:/var/run/docker.sock`) into the CI container, which gives the container full control over the host's Docker daemon β€” a significant security risk. A safer alternative is to use BuildKit's `–allow security.insecure` flag or purpose-built tools like Kaniko or Buildah, which build OCI images without requiring a Docker daemon.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started