How To Install Docker on Ubuntu 26.04 LTS

Install Docker on Ubuntu 26.04

If you’ve just spun up a fresh Ubuntu 26.04 LTS box and typed sudo apt install docker.io out of habit, stop right there. That command still works, technically, but it hands you a Docker build that’s often several versions behind what Canonical’s own repo maintainers snapshot for the release. On a laptop for weekend tinkering, fine. On a production host that’s about to run customer-facing containers, that gap can mean missing security patches, an outdated Compose plugin, or subtle behavior differences that bite you at 2 a.m. during an incident.

Ubuntu 26.04 LTS, codenamed “Resolute Raccoon,” shipped in April 2026 and it’s not a minor bump from 24.04. It moved to the 7.0 kernel line, went Wayland-only on the desktop side, adopted sudo-rs as the default sudo implementation, and quietly reshuffled how some APT keyring paths behave. None of that breaks Docker installation in a dramatic way, but it does change a few of the “gotchas” that used to trip people up on older LTS releases, and introduces a couple of new ones worth knowing about before you touch a terminal.

This guide walks through installing Docker Engine the way Docker’s own engineering team recommends: via their official APT repository, not the Ubuntu universe package. It covers the GPG key setup, the actual install commands, post-install configuration that almost nobody skips in real deployments (rootless mode, non-root user access, log rotation), firewall considerations that matter more than people think, and a troubleshooting section built from the kinds of errors that actually show up in support tickets, not textbook hypotheticals.

Whether you’re provisioning a single VPS for a side project or standardizing a fleet of 26.04 nodes behind a load balancer, the process below has been tested against a stock Resolute Raccoon install with no prior Docker packages present. Let’s get into it.

Why the Official Docker Repository Beats the Ubuntu Default

Ubuntu maintains its own docker.io package in the universe repository, and Canonical has gotten better about keeping it reasonably current over the years. But “reasonably current” isn’t the same as “current.” Docker Inc. ships Docker Engine, containerd, Buildx, and the Compose plugin as a coordinated set through download.docker.com, and that’s the version stream that gets security fixes first, gets tested against the newest Compose spec, and matches what you’ll see referenced in Docker’s own documentation and CVE advisories.

There’s a practical reason this matters beyond version numbers, too. If you’re running a CI/CD pipeline that builds multi-platform images with Buildx, or you rely on BuildKit cache mounts, the Ubuntu-packaged docker.io sometimes lags on plugin compatibility. On 26.04 specifically, the archived docker.io metapackage at the time of writing tracks Docker 26.x-era behavior, while the official repo already serves Docker 29, which is the version bundled by default in fresh Resolute Raccoon installs according to Canonical’s own release notes. Two major versions apart is not a rounding error.

A Quick Reality Check on sudo-rs

One change in 26.04 that trips up people copying old tutorials: the default sudo binary is now sudo-rs, a memory-safe Rust reimplementation. For 99% of the commands in this guide it behaves identically to classic sudo, but if you’ve written automation scripts that parse sudo -V output or rely on obscure sudoers directives from the original codebase, test them first. It won’t affect the install steps below, but it’s the kind of detail that saves you a confused hour later.

Prerequisites Before You Touch APT

Before running a single install command, confirm the basics. Skipping this step is how people end up debugging phantom errors that are actually just stale package lists or an unsupported architecture.

  • A fresh or updated Ubuntu 26.04 LTS system, either bare metal, VM, or cloud instance (AWS, DigitalOcean, Hetzner, and most VPS providers already offer 26.04 images).
  • A user account with sudo privileges. Don’t do this as raw root unless you have a very specific reason.
  • At least 20GB of free disk space if you plan to pull and build images regularly. Docker images and layers accumulate fast, and running out of inodes on /var/lib/docker is a classic production headache.
  • Outbound internet access on port 443, since both the GPG key fetch and the APT repo pull happen over HTTPS.

Run a full update first:

sudo apt update && sudo apt upgrade -y

This isn’t ceremonial. Kernel and glibc mismatches from a stale system are one of the more common (and more annoying) sources of container runtime weirdness, especially around cgroup v2 handling, which 26.04 enforces more strictly than earlier releases did.

Step 1: Remove Conflicting Packages

If this is a genuinely fresh install, skip ahead. But if you’ve inherited a server, or you’re rebuilding one that had Docker on it previously, purge the old conflicting packages first. Docker’s installer will complain (or worse, half-install) if these are still present:

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
    sudo apt remove -y $pkg
done

Don’t panic if apt says several of these aren’t installed. That’s expected on a clean system. What you’re really guarding against is a half-configured containerd from a previous distro-packaged Docker attempt, which can leave orphaned systemd unit files that conflict with the official package’s units later.

Step 2: Set Up Docker’s Official APT Repository

This is the part people rush through and then wonder why apt update throws GPG errors. Do it in order.

First, install the packages needed to add a repo securely over HTTPS:

sudo apt update
sudo apt install -y ca-certificates curl

Create the keyrings directory (Ubuntu has used /etc/apt/keyrings as the standard location since 22.04, and it’s still the convention on 26.04):

sudo install -m 0755 -d /etc/apt/keyrings

Download Docker’s GPG signing key directly into that directory:

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Now register the repository. Docker’s newer documentation recommends the deb822-style .sources format over the legacy one-line .list file, and it’s genuinely a cleaner format for anyone who’s ever had to eyeball a broken sources line at 3 a.m.:

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

That $(. /etc/os-release && echo ...) snippet automatically resolves to resolute (or whatever the internal codename maps to) so you don’t have to hardcode it. This matters because hardcoding codenames is exactly how tutorials go stale a year after publishing.

Refresh your package index:

sudo apt update

If this fails with a signature error, don’t just re-run it hoping it fixes itself. Jump to the troubleshooting section below; there’s a specific fix for that.

Step 3: Install Docker Engine and the Plugin Suite

Install the full stack in one shot. This includes Docker Engine, the CLI, containerd, Buildx (for multi-platform image builds), and the Compose plugin (the modern docker compose subcommand, not the old standalone docker-compose binary):

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

On a clean 26.04 system this pulls Docker Engine 29.x as the default, matching what Canonical bundles into fresh installs of the release. If you need a pinned version for compliance or reproducibility reasons, which is common in regulated environments, list what’s available first:

apt list --all-versions docker-ce

Then install the exact string:

VERSION_STRING=5:29.8.0-1~ubuntu.26.04~resolute
sudo apt install -y docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin docker-compose-plugin

Pinning like this is worth building into your provisioning scripts (Ansible, cloud-init, whatever you use) so that a fleet of servers doesn’t silently drift onto different Docker minor versions over six months of unattended-upgrades.

Step 4: Verify the Installation

Check the Docker daemon is actually running:

sudo systemctl status docker

You should see active (running). If it’s enabled too, Docker will survive a reboot without you needing to remember to start it manually.

Run the canonical smoke test:

sudo docker run hello-world

If you see the “Hello from Docker!” message, the daemon, the container runtime, and the network bridge are all functioning correctly. This single command actually validates a surprising amount: image pulling from Docker Hub, container creation, and basic networking all in one go.

Step 5: Let Non-Root Users Run Docker Commands

Right now, every docker command needs sudo, which gets old fast and is also a mild security smell if you’re scripting things carelessly (anyone in the docker group effectively has root-equivalent access to the host, worth remembering). Still, for day-to-day usability:

sudo usermod -aG docker $USER

Log out and back in, or run newgrp docker to apply the group change without a full session restart. Test it:

docker run hello-world

No sudo needed this time. If you manage a multi-admin server, think carefully about who gets added to that docker group. It’s functionally equivalent to giving someone passwordless root, because a container can trivially mount the host filesystem.

Post-Install Configuration That Production Systems Actually Need

Getting Docker running is the easy 20%. The remaining 80% is configuration most tutorials skip, and it’s exactly the stuff that separates a lab setup from something you’d trust with real traffic.

Configure the Storage Driver and Log Rotation

By default, Docker’s json-file logging driver has no size cap. Leave it alone on a chatty application and you’ll eventually watch /var/lib/docker eat your entire disk, usually discovered when something else fails because there’s no space left. Set sane limits in /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}

Restart Docker to apply it:

sudo systemctl restart docker

overlay2 has been the recommended storage driver for years now and 26.04’s kernel 7.0 line has no issues with it. Don’t chase exotic storage drivers unless you have a very specific workload reason.

Enable Rootless Mode for Higher-Security Environments

If you’re running Docker on a shared host, or you just don’t like the idea of the Docker daemon running as root (a fair concern), rootless mode is worth the extra setup time. It runs the entire daemon inside a user namespace:

sudo apt install -y uidmap dbus-user-session
dockerd-rootless-setuptool.sh install

Rootless mode has trade-offs: certain networking configurations (particularly things involving privileged ports below 1024) need extra steps, and performance on overlay networks can be marginally slower. For most internal tools and CI runners, though, it’s a worthwhile security upgrade, and it’s mature enough now on 26.04 that it’s not the experimental feature it was several LTS cycles ago.

Firewall Rules: The Part People Forget

Here’s something that catches even experienced admins off guard: Docker manipulates iptables directly, and it does so in a way that can bypass UFW rules you think are protecting a service. If you expose a container port with -p 8080:8080, Docker inserts its own iptables rules ahead of UFW’s chain, meaning that port can be reachable from the outside internet even if UFW shows it as denied.

The fix, if you’re using UFW, is to explicitly manage Docker’s interaction with it rather than assuming UFW has final say:

sudo ufw allow from 172.17.0.0/16 to any port 2375 proto tcp

Better yet, avoid binding container ports to 0.0.0.0 unless you genuinely need external access. Bind to 127.0.0.1 for internal services and let a reverse proxy (Nginx, Traefik, Caddy) handle the actual public-facing termination:

docker run -p 127.0.0.1:8080:8080 myapp

This is a habit worth building early. It’s saved more than one production deployment from an accidental open database port.

Set Resource Limits at the Container Level

On a shared host running multiple containers, one runaway process can starve everything else of CPU and memory. Set explicit limits per container instead of hoping for the best:

docker run -d --memory="512m" --cpus="1.5" --name myapp myapp:latest

For anything beyond a handful of containers, this is where Docker Compose earns its keep, since you can define resource limits declaratively per service in a compose.yaml file and keep the whole stack’s constraints in version control.

Troubleshooting Common Docker Installation Errors on Ubuntu 26.04

“NO_PUBKEY” or GPG Signature Verification Failed

This almost always means the GPG key wasn’t downloaded correctly, or a stale key from an old installation attempt is conflicting. Delete and redo it:

sudo rm /etc/apt/keyrings/docker.asc
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo apt update

“Cannot connect to the Docker daemon” After Fresh Install

Usually means the daemon isn’t running, or your shell session hasn’t picked up the group membership change yet:

sudo systemctl status docker
sudo systemctl start docker
groups $USER

If docker isn’t listed in your groups output after adding yourself with usermod, log out completely and back in. newgrp docker works in the current shell but doesn’t propagate to every process.

Package Held Back or Version Conflicts During apt install

This tends to happen when a partial or interrupted previous install left containerd.io at a version that conflicts with the requested docker-ce version. Clean it up:

sudo apt install -f
sudo dpkg --configure -a
sudo apt update && sudo apt upgrade -y

Containers Can’t Reach the Internet (DNS Resolution Failing Inside Containers)

Common on hosts using systemd-resolved with unusual DNS configurations. Check what Docker’s actually using:

cat /etc/resolv.conf

If it points to a stub resolver at 127.0.0.53 and containers can’t reach that (they can’t, since it’s a host-local loopback address), explicitly set DNS in daemon.json:

{
  "dns": ["8.8.8.8", "1.1.1.1"]
}

Restart Docker afterward. This one bites people constantly and it’s rarely mentioned in basic install guides.

“failed to create shim task: OCI runtime create failed” on Cgroup v2

Ubuntu 26.04 enforces cgroup v2 more strictly than earlier releases. If you’re running an older containerd config file inherited from a previous OS install, it may still reference cgroupfs instead of systemd as the cgroup driver. Check and fix:

cat /etc/docker/daemon.json

Ensure it includes:

{
  "exec-opts": ["native.cgroupdriver=systemd"]
}

Then restart Docker. This mismatch is one of the more confusing errors because the symptom (a container refusing to start) gives almost no clue about the actual cgroup driver mismatch underneath it.

Performance Tuning Notes for Production Docker Hosts

A few tuning habits worth adopting rather than treating as optional:

  • Monitor disk usage on /var/lib/docker proactively with docker system df, and schedule periodic cleanup with docker system prune (carefully, and never blindly on a host with containers you can’t easily rebuild).
  • On hosts running many short-lived containers (CI runners, batch jobs), increase the default ulimit for open files, since Docker’s default can become a bottleneck under heavy concurrent workloads.
  • If you’re running database containers, mount data volumes on a separate disk or partition from the OS, ideally NVMe-backed, since overlay2 filesystem overhead compounds under heavy random I/O.
  • Use docker network sparingly. Every custom bridge network adds a small amount of iptables overhead; on hosts running hundreds of containers, this adds up and is worth auditing periodically.
r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.

Related Posts