How to Install Docker on Linux
Install Docker Engine on Ubuntu, Debian, Fedora, RHEL, or Arch from the official repository, configure the docker group, run a first container, and get Compose working.
Before you start
- ▸A 64-bit Linux host with kernel 4.0 or later (check with uname -r)
- ▸sudo or root access on the target machine
- ▸curl and gnupg installed (most distributions include these)
Docker is the de-facto standard for running containerised workloads on Linux. The packages shipped by most distribution repositories lag behind the upstream release cycle, so the recommended path is to add Docker's own repository and install from there. This guide covers the Docker Engine (not Docker Desktop), adds your user to the docker group, runs a first container, and gets Docker Compose working.
Prerequisites and a Note on Editions
Docker Engine is the open-source daemon you want on servers and workstations. Docker Desktop is a separate, GUI-centric product with its own licensing terms. Everything below installs Engine only. The steps assume a 64-bit x86 host; ARM64 (Raspberry Pi, AWS Graviton) is supported by the same repository—the architecture is detected automatically.
Remove Conflicting Packages
Older Docker packages shipped under different names. Remove them before you add the official repository, otherwise dependency conflicts will bite you later.
Debian / Ubuntu
for pkg in docker.io docker-doc docker-compose docker-compose-v2 \
podman-docker containerd runc; do
sudo apt-get remove -y $pkg 2>/dev/null
done
Fedora / RHEL / Rocky
sudo dnf remove -y docker docker-client docker-client-latest \
docker-common docker-latest docker-latest-logrotate \
docker-logrotate docker-selinux docker-engine-selinux docker-engine
Arch Linux
sudo pacman -Rns docker 2>/dev/null; true
Add the Docker Repository and Install
Debian / Ubuntu
Install the prerequisites, import Docker's GPG key, and add the repository in one pass.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
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
For Debian, replace ubuntu in the URL with debian. Then add the repository:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Fedora / RHEL 9 / Rocky 9
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
On RHEL or Rocky, use the CentOS repo URL instead:
sudo dnf config-manager --add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Arch Linux
Arch's community repository ships a reasonably current Docker build.
sudo pacman -Syu --needed docker docker-compose
Enable and Start the Docker Daemon
Docker runs as a systemd service. Enable it so it starts on boot, then start it immediately.
sudo systemctl enable --now docker
Confirm it is running:
sudo systemctl status docker
You should see Active: active (running). Output will vary by host and Docker version.
Add Your User to the docker Group
By default, the Docker socket (/var/run/docker.sock) is owned by root and the docker group. Running every command with sudo is inconvenient and error-prone. Adding your user to the group removes that requirement.
docker group is effectively equivalent to passwordless root access on the host—any user who can talk to the Docker socket can trivially escalate privileges. Only add trusted users on machines where that trade-off is acceptable. On shared or production systems, consider rootless Docker instead.
sudo usermod -aG docker $USER
The new group membership does not take effect in your current shell. Apply it without logging out by starting a new login shell:
newgrp docker
Verify you can reach the daemon without sudo:
docker info
Run Your First Container
The canonical smoke test pulls a minimal image and runs a one-shot container that prints a message and exits.
docker run --rm hello-world
Docker pulls the image from Docker Hub, creates a container, runs it, and removes it when it exits (--rm). A successful run ends with a line similar to:
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
Try something more interactive—an ephemeral Ubuntu shell that disappears cleanly when you exit:
docker run --rm -it ubuntu:24.04 bash
Install and Use Docker Compose
Modern Docker ships Compose as a CLI plugin (docker compose, no hyphen). If you installed the packages above it is already present. Confirm:
docker compose version
Typical output: Docker Compose version v2.27.0 (version will vary).
A Minimal Compose Example
Create a working directory and a compose.yaml file:
mkdir ~/compose-test && cd ~/compose-test
cat > compose.yaml <<'EOF'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: example
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
EOF
docker compose up -d
Check that both services are running:
docker compose ps
Then open http://localhost:8080 in a browser—you should see the nginx welcome page. Tear it down when you are done:
docker compose down -v
Configure the Daemon (Optional but Recommended)
Docker's daemon configuration lives in /etc/docker/daemon.json. A few settings worth knowing about on production hosts:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl restart docker
Without log rotation, long-running containers can fill your disk with JSON logs. The overlay2 storage driver is the default on modern kernels but stating it explicitly prevents surprises if defaults change.
Firewall Considerations
Docker manipulates iptables rules directly to handle port forwarding and container networking. This can interact unexpectedly with ufw, firewalld, or nftables front-ends—published ports may be reachable even when your firewall rules suggest otherwise.
- ufw users: Docker bypasses ufw by writing to the
DOCKERchain. Bind sensitive ports to127.0.0.1explicitly (e.g.,127.0.0.1:5432:5432) to avoid unintended exposure. - firewalld users: Docker 26+ has improved firewalld integration. Run
sudo firewall-cmd --reloadafter starting Docker if rules seem inconsistent. - Never disable Docker's iptables integration (
"iptables": false) unless you fully understand the networking consequences—container-to-container and NAT routing will break.
Troubleshooting
- Permission denied on the socket: You ran
dockerin a shell that predates theusermodchange. Runnewgrp dockeror open a fresh terminal. A full logout/login is the most reliable fix. - Cannot connect to the Docker daemon: The service is not running. Check
sudo systemctl status dockerand look atjournalctl -xeu dockerfor errors. - Repository GPG errors on Debian/Ubuntu: The key file must be readable by apt. Re-run the
chmod a+rstep and thensudo apt-get update. - SELinux denials on Fedora/RHEL/Rocky: Docker and SELinux coexist in enforcing mode, but volume mounts often need the
:zor:Zflag (e.g.,-v /host/path:/container/path:z) to relabel files correctly. - Compose v1 vs v2: The legacy
docker-composebinary (Python, hyphenated) is end-of-life. If a project requires it, the modern plugin is a drop-in replacement for nearly all workflows.
Frequently asked questions
- What is the difference between docker-compose (v1) and docker compose (v2)?
- Docker Compose v1 was a standalone Python binary invoked as docker-compose. V2 is a Go-based CLI plugin invoked as docker compose (no hyphen) and ships with Docker Engine packages. V1 is end-of-life; use V2 for all new work.
- Is adding my user to the docker group safe?
- It is convenient but carries real risk: any user in the docker group can mount the host filesystem into a container and gain root-equivalent access. On shared or multi-user servers, use rootless Docker or require sudo instead.
- Why does Docker bypass my ufw or firewalld rules?
- Docker writes directly to iptables to manage container NAT and port forwarding, which can expose published ports even when your firewall front-end says they are blocked. Bind sensitive ports to 127.0.0.1 in your Compose or run command to limit exposure.
- Should I use the distro-provided docker.io package instead?
- The distro packages are often significantly behind the upstream release. For anything beyond casual experimentation, the official Docker repository is preferable because you get current Engine versions, Buildx, and the Compose plugin together.
- How do I update Docker after this installation?
- Because Docker is now in a managed repository, updates come through your normal package manager: apt-get upgrade on Debian/Ubuntu, dnf upgrade on Fedora/RHEL, or pacman -Syu on Arch. No manual steps required.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.