---
id: 2026-08-20-install-and-use-docker-on-ubuntu-26-04
slug: install-and-use-docker-on-ubuntu-26-04
title: "Install and configure Docker on Ubuntu 26.04 LTS"
excerpt: "Install Docker on Ubuntu 26.04 LTS: from the official APT repository through daemon tuning and log rotation to UFW firewall integration."
date: "2026-08-20T10:00:00+02:00"
updated: "2026-08-20T10:00:00+02:00"
author:
  name: "Sebastian Palencsár"
  handle: "spalencsar"
category: "server-environments"
tags: ["docker", "ubuntu2604", "container", "linuxadmin", "server", "devops", "docker-compose"]
reading_time: 25
toc: true
---

On [Ubuntu 26.04 LTS (Resolute Raccoon)](/en/tag/ubuntu2604){.badge-link-text} Docker is the most widely used platform for container-based isolation of server services. Unlike full virtual machines, containers share the host system's Linux kernel. Isolation of processes, filesystems and network stacks is native through Linux kernel mechanisms: `Control Groups (cgroups v2)` and `Kernel Namespaces`.

The following setup puts official Docker Community Edition (CE) onto Ubuntu 26.04 LTS. It covers safe handling of the Docker GPG key under `/etc/apt/keyrings`, daemon configuration for automated log rotation, permission control through the `docker` group, and how that interacts with packet filters and firewalls such as UFW.

<blockquote class="infobox infobox--practice">
❗ **Important note:** The audience is **Linux administrators and IT professionals** who already have basic Ubuntu and command-line experience. You should be comfortable in the terminal and understand what package management means. At the same time the material is suitable for **beginners in container technology** who want to learn Docker from the ground up.
</blockquote>

<blockquote class="infobox infobox--warn">
⚠️ **Privilege level of the Docker daemon:** The Docker daemon (`dockerd`) runs with full root rights by default. Anyone with access to the Docker socket `/var/run/docker.sock` has de facto unrestricted root access to the base system. Run containers in production on the least-privilege principle and grant group memberships restrictively.
</blockquote>

## 1. Architecture and how it works: containers vs. virtual machines

To run Docker stably in server operation, the distinction from hypervisor-based virtualization (such as KVM or Proxmox VE) is essential. Virtual machines emulate complete hardware resources and boot a standalone guest operating system. Containers, by contrast, wrap processes on the already running host kernel:

```markdown
┌─────────────────────────────────────────────────────────────┐
│   Virtualization vs. containerization                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. Virtual machines (hypervisor virtualization):          │
│   ┌─────────────────┬─────────────────┬───────────┐         │
│   │ App A | Libs    │ App B | Libs    │ App C...  │         │
│   ├─────────────────┼─────────────────┼───────────┤         │
│   │ Guest OS        │ Guest OS        │ Guest OS  │         │
│   ├─────────────────┴─────────────────┴───────────┤         │
│   │ Hypervisor (type 1/2, e.g. KVM, ESXi, QEMU)   │         │
│   ├───────────────────────────────────────────────┤         │
│   │ Host operating system and hardware            │         │
│   └───────────────────────────────────────────────┘         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   2. Containers (kernel isolation via ns and cgroups):      │
│   ┌─────────────────┬─────────────────┬───────────┐         │
│   │ App A | Libs    │ App B | Libs    │ App C...  │         │
│   ├─────────────────┴─────────────────┴───────────┤         │
│   │ Docker Engine / container runtime (containerd)│         │
│   ├───────────────────────────────────────────────┤         │
│   │ Shared host Linux kernel (namespaces/cgroups) │         │
│   ├───────────────────────────────────────────────┤         │
│   │ Host OS (Ubuntu 26.04 LTS) and hardware       │         │
│   └───────────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────┘
```

The Docker Engine itself is several modular layers:

```markdown
┌─────────────────────────────────────────────────────────────┐
│   Docker Engine architecture and component stack            │
│ 1. Docker CLI (command line):                               │
│    docker run / docker build / docker compose               │
│    └──────────────┬─────────────────────────────────────────┤
│                   ▼ REST API via /var/run/docker.sock       │
│ 2. Docker daemon (dockerd):                                 │
│    Image builds, network drivers, storage (overlay2)        │
│    └──────────────┬─────────────────────────────────────────┤
│                   ▼ gRPC interface                          │
│ 3. Container runtime (containerd):                          │
│    Image distribution, snapshot management, metadata        │
│    └──────────────┬─────────────────────────────────────────┤
│                   ▼ OCI specification                       │
│ 4. OCI runtime (runc):                                      │
│    Creates namespaces (pid, net, mnt) and cgroups v2        │
└─────────────────────────────────────────────────────────────┘
```

### Core kernel mechanisms behind Docker

* **Namespaces:** Isolate visibility. The `pid` namespace makes a process inside the container see its own PID list (starting at PID 1). The `net` namespace assigns the container its own virtual network interfaces and routing tables. `mnt` isolates mount points, `ipc` shared memory regions and `user` user IDs.
* **Control Groups (cgroups v2):** Limit and monitor resources. Through cgroups you set how much memory, CPU cycles or disk I/O a container may consume at most, so faulty applications cannot create a denial-of-service condition.
* **Storage driver (`overlay2`):** Builds a writable layer filesystem on copy-on-write (CoW). The base image stays immutable (`read-only`), while changes are stored in a thin upper layer (`upperdir`).

### Distinction: when containers and when VMs?

| Criterion | Docker container | Virtual machine (KVM / Proxmox) |
| --- | --- | --- |
| **Kernel** | Shared host kernel | Own kernel per guest |
| **Start time** | Milliseconds to seconds | 10 to 60 seconds (boot) |
| **RAM overhead** | Low (only actual process demand) | High (host reserves fixed guest RAM) |
| **Security boundary** | Shared kernel isolation (namespaces) | Strict hardware virtualization |
| **Operating systems** | Linux only on a Linux host | Any (Linux, BSD, Windows) |
| **Kernel modules** | Not loadable by unprivileged containers | Full kernel control in the guest |

## 2. Check system requirements and prepare the base system

Before installing the official Docker repository, check system architecture, kernel version and remove any leftover packages from earlier distribution exports.

### Hardware and architecture check

Docker requires a 64-bit architecture (`x86_64` or `amd64`, or `aarch64` / `arm64`). Run the query in the terminal:

```bash
# Verify system architecture and kernel version
uname -m && uname -r
```

The output must return `x86_64` or `aarch64`. For production servers with several running containers, a minimum of 2 vCPUs and 2–4 GB RAM is recommended so system buffers and container overlays sit cleanly in memory.

Verify the distribution codename of Ubuntu 26.04 LTS:

```bash
# Query distribution data
lsb_release -a
```

### Remove leftovers and distribution default packages

Ubuntu ships historical packages such as `docker.io` or outdated containerd builds in the default package sources. To rule out version conflicts with official upstream packages from Docker Inc., remove them completely:

```bash
# Clean old distribution packages
sudo apt remove -y docker docker-engine docker.io containerd runc
```

If the reply says individual packages were not installed, that is the expected state.

### Update the system and install base tools

Bring the package index up to date and install the tools required for download and signature verification:

```bash
# Update the base system
sudo apt update && sudo apt upgrade -y

# Install required helper tools
sudo apt install -y ca-certificates curl gnupg lsb-release
```

<blockquote class="infobox infobox--practice">
❗ **Check kernel state:** If `apt upgrade` installed a new Linux kernel, a reboot (`sudo reboot`) is advisable before the Docker installation. Only then do kernel namespaces and `cgroups v2` run on the latest security baseline.
</blockquote>

## 3. Add the official Docker APT repository

The official repository guarantees access to current Docker CE versions, security patches and official plugins such as Buildx and Compose.

### Set up the GPG keyring safely

Under current Debian and Ubuntu practice, repository keys are no longer stored via the obsolete `apt-key` or globally under `/etc/apt/trusted.gpg`, but in a dedicated directory under `/etc/apt/keyrings` with strict file permissions:

```bash
# Create the keyring directory with restrictive permissions
sudo install -m 0755 -d /etc/apt/keyrings

# Download the official Docker GPG key and store it as dearmored ASCII
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

# Ensure read rights for all system services and APT
sudo chmod a+r /etc/apt/keyrings/docker.asc
```

### Add the repository to the APT source list

Register the package source with a reference to the downloaded signing key:

```bash
# Set up the Docker APT source
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

Refresh the APT package index so the new packages are read:

```bash
# Sync the package index with the new source
sudo apt update
```

In the console output, the line `Get:... https://download.docker.com/linux/ubuntu ... InRelease` confirms that APT contacted the repository successfully.

## 4. Install Docker Engine and container tools

Install the full Docker toolset consisting of the engine, the CLI, containerd and the official plugins:

```bash
# Install Docker CE, containerd and modern plugins
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

The installed packages do the following:
* `docker-ce`: The actual daemon service (`dockerd`).
* `docker-ce-cli`: The command-line interface (`docker`) that talks to the daemon REST API.
* `containerd.io`: The open-source container runtime to the OCI standard.
* `docker-buildx-plugin`: Modern build tooling for multi-architecture images (Moby BuildKit).
* `docker-compose-plugin`: The native Compose command (`docker compose`) as a direct CLI plugin.

### Verify service status and version

Check the runtime status of the systemd service:

```bash
# Query Docker service status
sudo systemctl status docker --no-pager
```

The output must show `Active: active (running)`. Check the installed versions:

```bash
# Check the engine version
sudo docker version

# Check the Docker Compose version
docker compose version
```

<blockquote class="infobox infobox--info">
💡 **Note on Docker Compose v2:** Modern Docker Compose is designed as a CLI plugin and is invoked with a space (`docker compose`). The historical, separate Python binary `docker-compose` (with a hyphen) is obsolete and is no longer used.
</blockquote>

### Run the installation function test

Start the standardised verification container:

```bash
# Run the official function-test container
sudo docker run --rm hello-world
```

This call makes the Docker Engine pull the `hello-world` image from Docker Hub, instantiate a short-lived container and print the confirmation `Hello from Docker!` to stdout. The `--rm` parameter tells Docker to remove the container from the filesystem immediately after it exits.

## 5. Permission model: set up Docker without sudo

By default the Docker daemon binds its control socket `/var/run/docker.sock` to user `root` and group `docker`. Regular system users cannot talk to the socket without root rights.

To run Docker commands day to day without a leading `sudo`, add the administrative user to the `docker` system group:

```bash
# Add the current user to the docker group
sudo usermod -aG docker $USER
```

For the group change to take effect in the running shell, either log in again or activate the group rights directly in the current terminal session:

```bash
# Apply group membership in the current shell immediately
newgrp docker
```

Verify unprivileged access without `sudo`:

```bash
# Test run as a normal user
docker run --rm alpine echo "Container runs without sudo"
```

<blockquote class="infobox infobox--warn">
⚠️ **Security impact of the docker group:** Adding a user to the `docker` group grants effective root on the host. A user with access to the Docker socket can mount arbitrary host directories (`/etc`, `/root`) into a container (`docker run -v /:/host-root ...`) and manipulate them. Add only trusted administration accounts.
</blockquote>

## 6. Daemon configuration: logging, storage and performance

Without targeted configuration, Docker writes container logs to disk without a limit by default. On production services with busy traffic this sooner or later fills the root partition.

Put a central configuration file `/etc/docker/daemon.json` in place to cap logging and pin the storage driver explicitly:

```bash
# Create the configuration directory
sudo mkdir -p /etc/docker

# Create the central daemon configuration
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "live-restore": true
}
EOF
```

The configured directives do the following:
* `log-driver: json-file`: Writes standard logs in JSON format, compatible with `docker logs`.
* `max-size: 10m` and `max-file: 3`: Rotates logs automatically at 10 MB and keeps at most 3 generations. A container thus never occupies more than 30 MB of disk for logs.
* `storage-driver: overlay2`: The recommended, performant storage driver for Linux filesystems (`ext4`, `xfs`).
* `live-restore: true`: Keeps containers running even when the Docker daemon is restarted (for example during package updates or service restarts). That cuts downtime.

Reload the Docker daemon to activate the settings:

```bash
# Restart the Docker daemon
sudo systemctl restart docker

# Verify the applied configuration
docker info | grep -E '(Logging Driver|Storage Driver|Live Restore)'
```

## 7. Firewall integration and kernel parameters (UFW vs. Docker)

One of the most common security traps in Linux server operation is the automatic interplay of Docker and the UFW packet-filter firewall.

### Understand the UFW bypass trap

When publishing a port (`-p 8080:80`), Docker rewrites the `PREROUTING` chain of `iptables`/`nftables` directly. Those rules fire **before** UFW filter rules are evaluated. A port you have not opened in UFW is still reachable from outside if a container is bound with `-p 0.0.0.0:Port:Port`.

<blockquote class="infobox infobox--practice">
❗ **Security rule for port bindings:** Always bind container ports for internal services (for example databases or caches) explicitly to localhost (`127.0.0.1`) when they must not be exposed to the internet:
```bash
# Safe: the service listens only on the local loopback interface
docker run -d -p 127.0.0.1:3306:3306 mariadb
```
</blockquote>

### Enable kernel parameters for bridge network traffic

So that packet filters such as `iptables` can filter traffic over virtual Linux bridges (`docker0`) properly, the matching kernel modules and sysctl flags must be set:

```bash
# Store sysctl parameters for bridge network traffic
sudo tee /etc/sysctl.d/99-docker-bridge.conf > /dev/null <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

# Load the parameters immediately without a reboot
sudo sysctl --system
```

## 8. Practical use: container lifecycle and Docker Compose

After the base installation, two typical operating scenarios: manual container control through the CLI, and declarative multi-container operation via Docker Compose.

### Practical container lifecycle on a web service

🔧 **Practical example:**

Start an Nginx web server as a background service (detached mode), bind it to port 8080, inspect the logs and run an interactive command inside the container:

```bash
# Start the Nginx container in the background
docker run -d --name webserver -p 8080:80 nginx:alpine

# Check running status and port mappings
docker ps

# View real-time logs of the container
docker logs webserver

# Run a command directly in the container namespace
docker exec -it webserver nginx -v

# Test HTTP reachability from the host
curl -I http://127.0.0.1:8080

# After a successful test, stop and remove the container cleanly
docker stop webserver && docker rm webserver
```

### Declarative operation with Docker Compose

In professional server operation, container configurations are rarely started through long ad-hoc commands, but versioned in a `compose.yaml` file.

Create a project directory and the configuration file:

```bash
# Create the project directory
mkdir -p ~/compose-demo && cd ~/compose-demo

# Create the declarative Compose configuration
tee compose.yaml <<'EOF'
services:
  web:
    image: nginx:alpine
    container_name: demo-web
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro

  redis:
    image: redis:alpine
    container_name: demo-redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

volumes:
  redis-data:
EOF

# Prepare a local HTML directory with a demo file
mkdir -p html
echo "<h1>Docker Compose on Ubuntu 26.04 is running</h1>" > html/index.html

# Start the multi-container stack in the background
docker compose up -d

# Check the status of every service in the stack
docker compose ps

# Shut the stack down when finished
docker compose down
```

## 9. Storage management and cleanup workflows

In day-to-day operation, image updates, stopped test containers and build caches accumulate orphaned data under `/var/lib/docker`.

### Analyse storage use

Use the built-in Docker commands to see how much space images, containers, volumes and the build cache occupy:

```bash
# Show detailed storage use
docker system df
```

The output breaks down exactly which share of storage is in active use and what percentage is marked `Reclaimable`.

### Targeted cleanup of orphaned resources

🔧 **Practical example:**

After several deployments a server shows high storage use in the build cache and unused images. Clean the system in a controlled way:

```bash
# Remove unused containers, networks and untagged dangling images
docker system prune -f

# Thorough cleanup: remove all unused images (not only dangling images)
docker image prune -a --filter "until=168h" -f
```

<blockquote class="infobox infobox--warn">
⚠️ **Watch volumes:** The command `docker system prune` does **not** delete Docker volumes by default, to prevent accidental data loss (for example database data). Only the explicit extra `--volumes` removes orphaned volumes permanently.
</blockquote>

## 10. Autostart and systemd hardening

So that Docker and every container with restart policies (`restart: unless-stopped` or `restart: always`) come up reliably after a host reboot, enable the systemd services:

```bash
# Ensure autostart for Docker Engine and containerd
sudo systemctl enable docker.service containerd.service
```

### End-to-end verification of the whole system

Finally run a complete test that combines name resolution, filesystems and process isolation:

```bash
# Full end-to-end function test with Alpine Linux
docker run --rm alpine sh -c "uname -a && ping -c 1 -W 2 dns.google"
```

If the console shows the kernel output plus a successful ping transfer (`1 packets transmitted, 1 packets received`), the Docker Engine on Ubuntu 26.04 LTS is working correctly with functioning DNS and bridge network forwarding.

## Command Reference (Cheatsheet)

The following table summarises the most important administration commands for daily Docker work:

| Command | Category | Purpose and operational effect |
| --- | --- | --- |
| `docker run -d --name web -p 8080:80 nginx` | Lifecycle | Starts a container in the background with port forwarding from host 8080 to 80 |
| `docker ps` | Monitoring | Lists every currently running container with ID, image, status and ports |
| `docker ps -a` | Monitoring | Lists every container (including exited instances with exit codes) |
| `docker logs -f --tail 100 <container>` | Logging | Follows stdout/stderr of the container in a stream, limited to 100 lines |
| `docker exec -it <container> sh` | Debugging | Opens an interactive shell inside the running container namespace |
| `docker stop <container>` | Lifecycle | Sends SIGTERM (after timeout SIGKILL) for a clean shutdown |
| `docker rm <container>` | Lifecycle | Deletes stopped containers and frees their writable layer |
| `docker images` | Storage | Shows every locally cached container image with tags and sizes |
| `docker rmi <image>` | Storage | Removes a container image from the local cache (`/var/lib/docker`) |
| `docker system df` | Maintenance | Shows storage use of images, containers, volumes and BuildKit |
| `docker system prune -f` | Maintenance | Cleans stopped containers, unused networks and untagged images |
| `docker compose up -d` | Orchestration | Builds and starts every service defined in `compose.yaml` in the background |
| `docker compose down` | Orchestration | Stops and removes every container, network and internal link of the stack |

## Further Resources

| Resource | Link | Purpose and content |
| --- | --- | --- |
| **Docker documentation** | [Docker Engine Docs](https://docs.docker.com/engine/){.badge-link-text} | Official installation instructions, release notes and API reference |
| **Docker Compose** | [Docker Compose Docs](https://docs.docker.com/compose/){.badge-link-text} | Specification and documentation for multi-container orchestration |
| **Ubuntu Server Guide** | [Ubuntu Server Guide](https://ubuntu.com/server/docs){.badge-link-text} | Official system documentation for Ubuntu 26.04 LTS (Resolute Raccoon) |
| **Server hardening 2026** | [Linux server hardening: SSH and CrowdSec](/en/server-environments/linux-server-hardening-fido2-crowdsec){.badge-link-text} | Baseline hardening, SSH with FIDO2 and nftables firewalls |
| **LEMP stack Ubuntu 26.04** | [Ubuntu 26.04 LEMP guide](/en/server-environments/install-lemp-stack-on-ubuntu-26-04){.badge-link-text} | Companion article for Nginx with HTTP/3, MariaDB and PHP 8.5 |
| **Portainer CE web GUI** | [Install Portainer on Ubuntu](/en/linux-beginners/install-portainer-on-ubuntu-24-04-lts){.badge-link-text} | Graphical management of Docker containers, stacks and volumes through a web interface |

## Conclusion

On Ubuntu 26.04 LTS, Docker provides a highly resource-efficient platform for server services through native `cgroups v2` support and modern kernel overlays. Unlike a stock install, hardening the system — especially capping container logs through `daemon.json`, enabling `live-restore`, and knowing about the UFW bypass behaviour of iptables — produces stable production operation.

In day-to-day operation, administrators should watch two parameters above all: storage growth under `/var/lib/docker` through automated cleanup routines (`docker system prune`), and binding to localhost (`127.0.0.1`) so internal services are not exposed to the internet by accident. If you prefer to administer Docker containers, networks and multi-container stacks through a central web dashboard instead of the command line, the article [Install Portainer on Ubuntu](/en/linux-beginners/install-portainer-on-ubuntu-24-04-lts){.badge-link-text} has the turnkey setup.
