Install Docker on Ubuntu: APT, Snap, Rootless — Complete Guide 2026

Pick the right Docker install path on Ubuntu.

Page content

Installing Docker on Ubuntu should be simple, but in practice several Docker-shaped options compete for the same command name, each with different packaging, upgrade behavior, and security implications.

This guide compares every major install path so you can pick the one that fits your machine.

The options you will encounter include:

  • docker.io from Ubuntu repositories
  • docker-ce from Docker’s official APT repository
  • Docker from Snap
  • Docker Desktop
  • manually downloaded .deb packages
  • the Docker convenience script
  • rootless Docker

docker workplace

Although they all provide container tooling, they are not interchangeable packages. The best choice depends on whether the machine is a developer workstation, a CI runner, a small server, a self-hosting box, or a production host. My default recommendation is calm but firm: for most technical users on normal Ubuntu machines, install Docker Engine from Docker’s official APT repository. Use Ubuntu’s docker.io only when distribution integration matters more than upstream Docker packaging. Avoid the Snap package unless you specifically want Snap behavior and understand its limits. Rootless Docker is worth knowing about, but it is not automatically the best default for every machine.

This guide explains the tradeoffs, covers post-install security, and gives you clean installation paths for each method. Once Docker Engine is running, the Docker Cheatsheet is your daily command reference, and the Docker Compose Cheatsheet covers multi-container setups. Both sit alongside Git, VS Code, and CI/CD guides in Developer Tools: The Complete Guide to Modern Development Workflows.

Quick Recommendation

The table below summarizes which install path fits common scenarios.

Use case Recommended install
Developer workstation Docker official APT repo
CI runner Docker official APT repo, version pinned if needed
Small self-hosted server Docker official APT repo
Production server Docker official APT repo, controlled upgrades
Ubuntu-only conservative system Ubuntu docker.io package
Quick desktop experiment Docker Desktop or official APT repo
Snap-managed Ubuntu setup Docker Snap, with caution
Strong non-root daemon requirement Rootless Docker
Air-gapped host Manual .deb packages or internal mirror

If you do not have a special reason to choose otherwise, Docker’s official APT repository is the default.

What Gets Installed

A normal Docker Engine setup includes several moving parts:

  • Docker daemon: dockerd
  • Docker CLI: docker
  • container runtime: containerd
  • low-level runtime: runc
  • Buildx plugin: docker buildx
  • Compose plugin: docker compose

Modern Docker Compose is usually installed as a Docker CLI plugin. That means the command is:

docker compose version

Not:

docker-compose version

The old docker-compose command still exists in older guides and older systems, but new Ubuntu setups should generally use the Compose plugin.

Option 1: Install Docker from Docker’s Official APT Repository

This is the best default for most developers and DevOps users. You get Docker’s upstream packaging, current Docker Engine releases, Buildx, Compose plugin, and a normal APT upgrade path.

Remove Conflicting Packages First

Before installing Docker CE, remove packages that may conflict with the official Docker packages.

sudo apt remove docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc

It is fine if APT says some of these packages are not installed.

This command does not remove Docker images, containers, volumes, or networks stored under /var/lib/docker. If you want a clean reset, that is a separate step and should be done deliberately.

Add Docker’s Official APT Repository

Install prerequisites:

sudo apt update
sudo apt install ca-certificates curl

Create the keyring directory:

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

Download Docker’s repository key:

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc

Allow APT to read the key:

sudo chmod a+r /etc/apt/keyrings/docker.asc

Add the Docker repository using the deb822 .sources format:

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<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

Update APT metadata:

sudo apt update

Install Docker Engine, Buildx, and Compose

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

Check the service:

sudo systemctl status docker

If it is not running:

sudo systemctl start docker

Verify the install:

sudo docker run hello-world

Check versions:

docker --version
docker buildx version
docker compose version

At this point Docker works, though you still need sudo for most commands unless you configure non-root access in the post-install section below.

Option 2: Install Docker from Ubuntu Repositories

Ubuntu provides the docker.io package, which you can install with:

sudo apt update
sudo apt install docker.io docker-compose-v2

Start and enable Docker:

sudo systemctl enable --now docker

Verify:

sudo docker run hello-world

When Ubuntu docker.io Makes Sense

Ubuntu’s package can be a good choice when:

  • You prefer Ubuntu-maintained packages.
  • You want a version aligned with Ubuntu’s release process.
  • You are managing many Ubuntu hosts with standard repositories.
  • You do not need the newest upstream Docker release.
  • You want fewer third-party APT sources.

This is a reasonable choice. It is not “wrong.”

When Ubuntu docker.io Is Not Ideal

Use Docker’s official repository instead when:

  • You want the current upstream Docker Engine.
  • You follow Docker’s own documentation.
  • You rely on current Buildx and Compose behavior.
  • You want Docker CE package names.
  • You are debugging issues against upstream Docker docs.
  • You need predictable Docker-version parity across distributions.

My bias: for developer machines and container-heavy hosts, use Docker’s official APT repository. For conservative Ubuntu-managed machines, docker.io is acceptable.

Option 3: Install Docker with Snap

The Docker snap installs with a single command, but simplicity does not always mean predictable behavior on a server or development machine.

sudo snap install docker

Snap packages have their own packaging model, update behavior, confinement assumptions, and filesystem layout. That is fine for many desktop apps, but Docker Engine is already a system-level container runtime, so the extra Snap layer can surprise people. If you manage other software with Snap, the Snap Package Manager Cheatsheet explains channels, confinement, and update behavior in more detail.

When Docker Snap Makes Sense

Docker Snap may be reasonable when:

  • You intentionally manage software with Snap.
  • You are using Ubuntu Core or a Snap-heavy environment.
  • You want snap-style automatic updates.
  • You are experimenting and do not care about matching Docker’s upstream APT instructions.

Why I Usually Avoid Docker Snap

I usually avoid the Docker snap for development and server use because:

  • Most Docker documentation assumes the standard Docker Engine layout.
  • Troubleshooting paths can differ from APT installs.
  • Service management may feel less transparent.
  • Snap auto-updates can be inconvenient for infrastructure software.
  • Some bind mounts, sockets, and host integration details may surprise you.

If Docker is central to your workflow, install it like infrastructure rather than a casual desktop app — even when a Snap install looks tempting on the surface.

Option 4: Install Docker from Manual .deb Packages

Manual .deb installation is useful when:

  • The machine cannot use external APT repositories.
  • You are building an offline install process.
  • You mirror packages internally.
  • You need strict change control.

The tradeoff is maintenance, because you must download and install new packages manually whenever you upgrade.

A manual install usually requires these packages:

  • containerd.io
  • docker-ce
  • docker-ce-cli
  • docker-buildx-plugin
  • docker-compose-plugin

Install them with:

sudo dpkg -i ./containerd.io_*.deb \
  ./docker-ce_*.deb \
  ./docker-ce-cli_*.deb \
  ./docker-buildx-plugin_*.deb \
  ./docker-compose-plugin_*.deb

Then fix missing dependencies if needed:

sudo apt --fix-broken install

Verify:

sudo systemctl status docker
sudo docker run hello-world

Manual .deb installs are not my first choice, but they remain valid for controlled or air-gapped environments where explicit package approval matters more than convenience.

Option 5: Use Docker’s Convenience Script

Docker provides a convenience script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

You can preview what it would do:

sudo sh get-docker.sh --dry-run

The convenience script is useful for disposable test machines, demos, labs, and throwaway environments, but I would not use it as the main install method for production systems. A script that configures repositories and installs packages non-interactively is convenient, yet long-lived infrastructure deserves explicit, reviewable steps — so for production hosts, use the APT repository method directly.

Docker Desktop vs Docker Engine on Ubuntu

Docker Desktop for Linux is a different product from Docker Engine. Docker Engine is the server-side runtime and CLI workflow most Linux server users expect, while Docker Desktop adds a GUI, Desktop integrations, and a product experience closer to macOS and Windows Docker usage.

Use Docker Desktop when:

  • You want a graphical Docker experience.
  • You want Desktop features.
  • You are aligning with a team that standardizes on Docker Desktop.
  • You do not mind the extra layer.

Use Docker Engine when:

  • You are running a server.
  • You want a simple Linux-native runtime.
  • You prefer systemd-managed services.
  • You are building CI, DevOps, or self-hosted infrastructure.
  • You do not need the GUI.

For an advanced technical blog audience, Docker Engine is usually the more interesting default on Linux servers and CI hosts.

Post-Install: Run Docker Without Sudo

After installation, this works:

sudo docker ps

But this may fail:

docker ps

That is because the Docker daemon listens on a Unix socket owned by root. The common fix is to add your user to the docker group.

Create the group if needed:

sudo groupadd docker

Add your user:

sudo usermod -aG docker $USER

Apply the new group membership:

newgrp docker

Or log out and log back in.

Test:

docker run hello-world

Important Security Note About the Docker Group

The docker group is not a harmless convenience group. A user who can control the Docker daemon can usually get root-equivalent control of the host, so for a personal developer machine this is often acceptable, but on a shared server it is a serious access-control decision. Treat membership in the docker group like admin access, and if that feels too broad, consider rootless Docker instead.

Rootless Docker on Ubuntu

Rootless Docker runs the Docker daemon and containers as a non-root user. This is not the same as adding your user to the docker group — with the docker group, the daemon still runs as root, whereas in rootless mode the daemon itself runs as your user.

When Rootless Docker Makes Sense

Rootless Docker is useful when:

  • You want to reduce daemon-level root risk.
  • You are on a shared development machine.
  • You run user-owned containers.
  • You do not need every advanced networking and storage feature.
  • You want a safer default for experimental workloads.

When Rootless Docker May Be Annoying

Rootless Docker can be less convenient when:

  • You need privileged containers.
  • You rely on low port binding without extra setup.
  • You need some host networking patterns.
  • You expect behavior identical to rootful Docker.
  • You are following guides written for normal Docker Engine installs.

Rootless mode improves security posture, but it is not zero friction compared with a standard rootful install.

Install Rootless Docker

Install prerequisites:

sudo apt update
sudo apt install uidmap

If you installed Docker from DEB or APT packages, the rootless setup tool should be available:

dockerd-rootless-setuptool.sh install

If rootful Docker is already running and you want only rootless Docker, disable the system daemon:

sudo systemctl disable --now docker.service docker.socket

You may also need to remove the rootful socket:

sudo rm -f /var/run/docker.sock

After installing rootless Docker, the setup tool usually prints environment variables to add to your shell profile. They commonly look like this:

export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

Your UID may differ. Check the exact output from the setup tool.

Enable lingering if you want the user service to run after logout:

sudo loginctl enable-linger $USER

Check the user service:

systemctl --user status docker

Run a test container:

docker run hello-world

Rootful Docker vs Rootless Docker

Topic Rootful Docker Rootless Docker
Daemon user root normal user
Command convenience high medium
Compatibility highest good, but not perfect
Security posture weaker by default better by default
Privileged containers supported limited
Low ports simple needs extra setup
Server usage common possible, but plan carefully
Developer workstation common good for security-conscious users

My practical recommendation:

  • Use normal rootful Docker for a personal dev machine or simple server.
  • Add only trusted users to the docker group.
  • Use rootless Docker when user isolation matters.
  • Do not pretend the docker group is a security boundary.

Enable Docker at Boot

On Ubuntu, Docker installed from normal packages usually starts automatically, but it is worth confirming after a fresh install or migration.

Check:

systemctl is-enabled docker
systemctl is-enabled containerd

Enable manually if needed:

sudo systemctl enable docker.service
sudo systemctl enable containerd.service

Start now:

sudo systemctl start docker

Disable auto-start:

sudo systemctl disable docker.service
sudo systemctl disable containerd.service

For rootless Docker, use the user service:

systemctl --user enable docker
systemctl --user start docker

And enable lingering if needed:

sudo loginctl enable-linger $USER

Install or Verify Docker Compose

With Docker’s official APT repository, install Compose as a plugin:

sudo apt update
sudo apt install docker-compose-plugin

Verify:

docker compose version

If you installed Ubuntu’s package, you may use:

sudo apt install docker-compose-v2

Prefer:

docker compose up -d

Over the old style:

docker-compose up -d

The hyphenated docker-compose command belongs to the older standalone Compose era. Many systems still have it, but new documentation should use docker compose.

Check Which Docker You Have Installed

When you inherit a machine or debug a broken setup, these commands help identify which Docker packaging is actually in use.

Check the Docker binary:

which docker

Check package ownership:

dpkg -S "$(which docker)"

List Docker-related packages:

dpkg -l | grep -E 'docker|containerd|runc'

Check APT policy:

apt-cache policy docker-ce docker.io containerd.io docker-compose-plugin docker-compose-v2

Check Snap:

snap list | grep docker

Check service status:

systemctl status docker
systemctl status containerd

Check Docker server details:

docker info

If docker info fails without sudo, check your group membership:

groups

Migrate from Ubuntu docker.io to Docker CE

Stop Docker:

sudo systemctl stop docker

Remove Ubuntu packages:

sudo apt remove docker.io docker-compose docker-compose-v2 docker-doc containerd runc

Add Docker’s official APT repository using the steps above.

Install Docker CE:

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

Start Docker:

sudo systemctl start docker

Check existing containers:

docker ps -a
docker images
docker volume ls

Normally, removing packages does not delete /var/lib/docker, so your images, containers, and volumes may still exist. Still, back up important data first.

Migrate from Docker Snap to Docker CE

First inspect what exists:

snap list | grep docker
docker info

Stop workloads and back up important volumes or bind-mounted data.

Remove the snap:

sudo snap remove docker

Then install Docker CE from the official APT repository.

Be careful with data locations. Snap packages often use different paths and confinement rules. Do not assume that Docker Snap data will appear automatically under the standard /var/lib/docker path after migration.

For important services, export or back up data explicitly before switching package sources.

Pin a Docker Version with APT

For production-like systems, you may want controlled upgrades.

List available versions:

apt list --all-versions docker-ce

Install a specific version:

VERSION_STRING="5:29.0.0-1~ubuntu.24.04~noble"

sudo apt install docker-ce=$VERSION_STRING \
  docker-ce-cli=$VERSION_STRING \
  containerd.io \
  docker-buildx-plugin \
  docker-compose-plugin

Hold Docker packages:

sudo apt-mark hold docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Remove the hold later:

sudo apt-mark unhold docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Do this when you need predictable change windows. For a personal laptop, it may be unnecessary.

Firewall and Networking Notes

Docker modifies packet filtering rules to make container networking work, which matters if you use UFW, firewalld, nftables, or custom firewall rules.

Important points:

  • Published Docker ports may bypass naive UFW expectations.
  • Docker uses iptables integration.
  • Custom rules should account for Docker-created chains.
  • Server hardening should be tested with real container port mappings.
  • Do not assume ufw deny protects a port published by Docker.

Test exposed ports from another machine, not only from localhost.

Example:

docker run --rm -p 8080:80 nginx

Then from another host:

curl http://server-ip:8080

On servers, Docker networking is part of your security model, not an implementation detail you can ignore after install.

Common Errors and Fixes

Permission Denied on Docker Socket

Error:

permission denied while trying to connect to the Docker daemon socket

Fix options:

Use sudo:

sudo docker ps

Or add your user to the docker group:

sudo usermod -aG docker $USER
newgrp docker

Remember that the docker group is root-equivalent in practice, so treat group membership as a privileged access decision.

Docker Daemon Is Not Running

Check status:

sudo systemctl status docker

Start it:

sudo systemctl start docker

Check logs:

journalctl -u docker --no-pager -n 100

Conflicting Docker Packages

If installing Docker CE fails because of conflicts, remove old packages. If APT itself is in a bad state after adding the Docker repository, work through Ubuntu APT troubleshooting for broken packages and GPG errors before retrying.

sudo apt remove docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc

Then retry:

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

Compose Command Not Found

Check modern Compose:

docker compose version

Install plugin:

sudo apt install docker-compose-plugin

If you expected the old command:

docker-compose version

You may be following old documentation. Prefer updating the command to docker compose.

Old Root-Owned Docker Config

If you ran Docker with sudo before setting up group access, your user config may be owned by root.

Fix ownership:

sudo chown "$USER":"$USER" "$HOME/.docker" -R
sudo chmod g+rwx "$HOME/.docker" -R

Or remove the config if you do not need it:

sudo rm -rf "$HOME/.docker"

It will be recreated.

Cannot Connect to Rootless Docker

Check environment:

echo "$DOCKER_HOST"

Check user service:

systemctl --user status docker

Set the socket path if needed:

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

Add it to your shell profile only after confirming it is correct.

Uninstall Docker Engine

Remove Docker CE packages:

sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

Remove Docker data only if you really want to delete images, containers, and volumes:

sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

Remove Docker repository files:

sudo rm -f /etc/apt/sources.list.d/docker.sources
sudo rm -f /etc/apt/keyrings/docker.asc

Refresh APT:

sudo apt update

For Docker Snap:

sudo snap remove docker

For Ubuntu docker.io:

sudo apt purge docker.io docker-compose-v2

For most technical Ubuntu users, this is the clean path:

sudo apt remove docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc

sudo apt update
sudo apt install ca-certificates curl

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

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 tee /etc/apt/sources.list.d/docker.sources > /dev/null <<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

sudo apt update

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

sudo docker run hello-world

Then decide whether you want group access for convenience or rootless Docker for tighter isolation:

sudo usermod -aG docker $USER
newgrp docker
docker run hello-world

Final Opinionated Guidance

Docker installation on Ubuntu is an operational choice, not just a package choice. The method you pick affects upgrades, security boundaries, and how closely your host matches upstream Docker documentation.

My practical rules are:

  • Use Docker’s official APT repository for most developer and DevOps machines.
  • Use Ubuntu docker.io when Ubuntu repository consistency matters more than upstream freshness.
  • Avoid Docker Snap for serious container workflows unless you intentionally want Snap behavior.
  • Avoid the convenience script for production hosts.
  • Use modern docker compose, not old docker-compose.
  • Treat the docker group as privileged access.
  • Consider rootless Docker when user isolation matters.
  • Pin versions on production-like systems.
  • Test firewall behavior when publishing ports on servers.

The most boring Docker install is usually the best one: official APT repo, explicit keyring, Compose plugin, systemd service, understood permissions, and no mystery packaging layer in the middle. Once the engine is in place, multi-service workloads — including self-hosted LLM stacks — are a natural next step; for example, see Ollama in Docker Compose for running local models in containers. To keep a Compose stack running across reboots on one server, Run Docker Compose as a Linux Service with systemd walks through unit files and operational habits.

Read More

Subscribe

Get new posts on AI systems, Infrastructure, and AI engineering.