How to Install Docker and Docker Compose on Ubuntu: Complete Guide
This guide covers installing Docker Engine and Docker Compose on Ubuntu. The official apt repository method described below is the approach I use on all my servers — it is the most maintainable long-term.
Prerequisites
You need an Ubuntu machine (20.04 or newer) with sudo access. A clean $5/month VPS works fine — Docker itself is lightweight.
# Update existing packages
sudo apt update && sudo apt upgrade -yStep 1: Install Dependencies
Docker requires some prerequisite packages for the apt repository to work over HTTPS:
sudo apt install -y ca-certificates curl gnupg lsb-releaseStep 2: Add Docker's GPG Key
sudo mkdir -p /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.gpgStep 3: Add Docker's Repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullStep 4: Install Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-pluginStep 5: Install Docker Compose
Docker Compose v2 is now a Docker CLI plugin, not a separate binary:
sudo apt install -y docker-compose-plugin
# Verify
docker compose versionStep 6: Post-Install Setup
Running Docker as root is inconvenient and a security risk. Add your user to the docker group:
sudo usermod -aG docker $USER
newgrp docker # Activate group change in current sessionStep 7: Verify the Installation
# Run the hello-world container
docker run hello-world
# You should see: "Hello from Docker!"
# This message shows your installation appears to be working correctly.A docker-compose.yml Example
Here is the compose file I use for most of my web services:
version: "3.8"
services:
web:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./html:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf:ro
restart: unless-stopped
app:
build: .
env_file: .env
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: unless-stopped
volumes:
pgdata:Troubleshooting
Permission Denied
If you get "permission denied" when running docker commands:
# Check if your user is in the docker group
groups $USER
# If docker is not listed, log out and log back in, or run:
sudo usermod -aG docker $USERDocker Daemon Not Running
sudo systemctl status docker
sudo systemctl enable docker
sudo systemctl start dockerProxy Configuration
If you are behind a corporate proxy:
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:8080"
Environment="HTTPS_PROXY=http://proxy.example.com:8080"
Environment="NO_PROXY=localhost,127.0.0.1"
EOF
sudo systemctl daemon-reload
sudo systemctl restart dockerapt-key is Deprecated
If you see "Warning: apt-key is deprecated" in older tutorials, the method above (using /etc/apt/keyrings) is the modern replacement.
Wrap Up
Running Docker Without Sudo
After installing Docker, every command requires sudo unless you add your user to the docker group. This gets old fast.
# Without the group:
sudo docker ps
sudo docker run nginx
sudo docker logs my-container
# With the group:
docker ps
docker run nginx
docker logs my-containerSecurity note: the docker group grants root-equivalent privileges. Anyone in the docker group can run privileged containers and access the host filesystem. On production servers, consider using Rootless Docker instead.
Cleaning Up Unused Resources
Docker does not clean up after itself. Over time, dangling images, stopped containers, and unused volumes accumulate:
# List disk usage
docker system df
# Remove all unused data
docker system prune -a --volumes
# Remove only stopped containers
docker container prune
# Remove dangling images
docker image pruneI run docker system prune once a month on my servers. It typically frees 5-10GB of disk space.
Setting Resource Limits
Without limits, a single container can consume all host resources. Always set limits in production:
services:
app:
image: my-app:latest
deploy:
resources:
limits:
cpus: "0.5"
memory: "256M"
reservations:
cpus: "0.25"
memory: "128M"Or at runtime:
docker run -d --memory="256m" --cpus="0.5" my-appI learned this the hard way when a memory leak in a Node.js container OOM-killed my entire server. Now every container gets resource limits.
Quick Reference: Useful Docker Commands
Here is the cheat sheet I keep pinned in my terminal:
# Container lifecycle
docker ps # List running containers
docker ps -a # List all containers
docker stop $(docker ps -q) # Stop all containers
docker rm $(docker ps -aq) # Remove all containers
# Images
docker images # List images
docker pull nginx:alpine # Pull an image
docker rmi nginx # Remove an image
docker build -t my-app . # Build an image from Dockerfile
# Logs and debugging
docker logs -f container_name # Follow logs
docker exec -it container bash # Shell into a container
docker inspect container # View container details
docker stats # Live resource usage
# Networks
docker network ls
docker network create my-network
docker run --network my-network nginxUninstalling Docker
If something goes wrong or you need a clean slate:
sudo apt remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerdDocker installation on Ubuntu is straightforward once you know the right steps. The key is using Docker's official apt repository rather than the version in Ubuntu's default repos — the default repos are often months behind, and with Docker, you want the latest security patches.