How to Install Docker on Ubuntu and Debian (Complete Guide)

How to Install Docker on Ubuntu and Debian (Complete Guide)

✅ Tested on Ubuntu 24.04 LTS, Ubuntu 22.04, Debian 12 — Last updated: June 2026

Docker on Linux is the standard way to run containerized applications, and installing it correctly is essential for anyone working with modern software development or running local AI tools. This guide covers installing Docker Engine on Ubuntu and Debian, the Docker Compose plugin, common post-installation steps, and everything you need to start running containers immediately.

Table of Contents

What Is Docker and Why Use It

Docker packages applications and their dependencies into containers — lightweight, isolated environments that run consistently everywhere. Unlike virtual machines, containers share the host OS kernel, making them fast to start and efficient with memory.

Why Docker matters for Linux users:

  • Run any software without conflicts: Different apps with different Python/Node/Java versions, all isolated from each other
  • Works the same everywhere: A container that works on your laptop works on a server or VPS
  • Essential for AI tools: Open WebUI, LocalAI, Stable Diffusion WebUI — all have official Docker images that are the easiest installation method
  • Easy cleanup: Remove a container and all its files vanish. No leftover config files or conflicting libraries
  • Reproducible environments: Docker Compose files define your entire stack as code

Install Docker on Ubuntu 24.04 / 22.04

Do not install Docker from the Ubuntu snap store or the default Ubuntu repositories — those versions are outdated. Use Docker's official repository for the latest version.

Step 1: Remove Old Versions

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

Step 2: Set Up the Repository

# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

# Add Docker's GPG key
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

# Add the Docker repository
echo 
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 
  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 update

Step 3: Install Docker Engine

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

Step 4: Verify Installation

sudo docker run hello-world

# Expected output:
# Hello from Docker!
# This message shows that your installation appears to be working correctly.

Install Docker on Debian 12

# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Add Docker's GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository (Debian uses different URL)
echo 
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 
  https://download.docker.com/linux/debian 
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | 
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

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

Post-Installation Steps

Run Docker Without sudo

By default, Docker requires root. Add your user to the docker group to run containers without sudo:

sudo usermod -aG docker $USER

# Apply the group change (log out and back in, or run):
newgrp docker

# Test:
docker run hello-world   # no sudo needed now

⚠️ Security note: The docker group gives root-equivalent access. Only add trusted users.

Start Docker on Boot

sudo systemctl enable docker
sudo systemctl enable containerd
sudo systemctl status docker

Configure Log Rotation

Docker container logs can grow very large. Configure log rotation to prevent disk exhaustion:

sudo nano /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
sudo systemctl restart docker

Docker Compose

Docker Compose lets you define and run multi-container applications. If you installed Docker as shown above, the Compose plugin is already included as docker compose (with a space, not a hyphen).

docker compose version
# Docker Compose version v2.27.1

Example: Run Open WebUI with Docker Compose

Create a docker-compose.yml file:

version: '3.8'
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    ports:
      - "3000:8080"
    volumes:
      - open-webui:/app/backend/data
    environment:
      - OLLAMA_BASE_URL=http://host.docker.internal:11434
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped

volumes:
  open-webui:
docker compose up -d     # start in detached mode
docker compose logs -f   # follow logs
docker compose down      # stop and remove containers
docker compose pull      # update images

Essential Docker Commands

Images

docker pull ubuntu:24.04         # download an image
docker images                    # list local images
docker image ls                  # same as above
docker rmi ubuntu:24.04          # remove image
docker image prune               # remove unused images
docker image prune -a            # remove all unused images (including tagged)

Containers

# Run a container:
docker run ubuntu:24.04 echo "Hello World"

# Run interactively:
docker run -it ubuntu:24.04 bash

# Run in background (detached):
docker run -d --name myapp nginx

# Run with port mapping:
docker run -d -p 8080:80 --name webserver nginx

# Run with volume mount:
docker run -d -v /host/data:/container/data --name myapp myimage

# Run and auto-remove on exit:
docker run --rm ubuntu:24.04 ls -la

# List running containers:
docker ps

# List all containers (including stopped):
docker ps -a

# Stop a container:
docker stop myapp

# Start a stopped container:
docker start myapp

# Remove a container:
docker rm myapp

# Remove running container (force):
docker rm -f myapp

# Execute command in running container:
docker exec -it myapp bash

# View container logs:
docker logs myapp
docker logs -f myapp      # follow
docker logs --tail 50 myapp  # last 50 lines

# Container stats (CPU, memory, network):
docker stats

Volumes and Persistent Data

Containers are ephemeral — when you remove them, their filesystem changes are lost. Use volumes or bind mounts for persistent data.

Named Volumes (Recommended for Databases)

# Create a named volume:
docker volume create mydata

# Run container with the volume:
docker run -d -v mydata:/var/lib/mysql --name mysql mysql:8

# List volumes:
docker volume ls

# Inspect volume (see where it's stored):
docker volume inspect mydata

# Remove volume:
docker volume rm mydata

# Remove all unused volumes:
docker volume prune

Bind Mounts (Recommended for Development)

# Mount a specific directory from host:
docker run -d -v /home/jm/myapp:/app -p 3000:3000 node:20 node /app/server.js

# Or with the --mount syntax (more explicit):
docker run -d 
  --mount type=bind,source=/home/jm/myapp,target=/app 
  -p 3000:3000 node:20

Docker Networking

# List networks:
docker network ls

# Create a custom network:
docker network create mynetwork

# Connect containers on the same network (they can reach each other by name):
docker run -d --network mynetwork --name db postgres:16
docker run -d --network mynetwork --name app myapp

# Inside 'app', connect to 'db' using the hostname 'db'

# Inspect a network:
docker network inspect mynetwork

# Disconnect container from network:
docker network disconnect mynetwork mycontainer

Writing a Dockerfile

A Dockerfile is the recipe for building a custom Docker image. Here's a practical example for a Python Flask app:

# Dockerfile
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Copy and install dependencies first (layer caching optimization)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create non-root user (security best practice)
RUN useradd --create-home appuser && chown -R appuser /app
USER appuser

# Expose port
EXPOSE 5000

# Run the app
CMD ["python", "app.py"]
# Build the image:
docker build -t myflaskapp:1.0 .

# Run it:
docker run -d -p 5000:5000 myflaskapp:1.0

# Build with no cache (force rebuild all layers):
docker build --no-cache -t myflaskapp:1.0 .

Dockerfile Best Practices

  • Use specific base image tags (python:3.12-slim) not latestlatest changes and breaks builds
  • Copy requirements.txt before the source code — unchanged requirements reuse the cached layer
  • Use --no-cache-dir with pip and similar flags to keep images smaller
  • Run containers as non-root users
  • Use .dockerignore to exclude node_modules, .git, *.env from the build context
  • Prefer slim or alpine base images for smaller image size

Troubleshooting

Docker daemon not running

sudo systemctl start docker
sudo systemctl status docker

# Check logs:
sudo journalctl -u docker -n 50

Permission denied — cannot connect to Docker daemon

# If you just added yourself to the docker group, log out and back in.
# Or apply immediately with:
newgrp docker

# Verify group membership:
groups $USER | grep docker

Port already in use

# Find what's using the port:
ss -tulnp | grep :8080

# Stop the conflicting process or use a different port:
docker run -d -p 8081:80 nginx  # use 8081 instead

Out of disk space

# Check Docker's disk usage:
docker system df

# Clean up everything unused:
docker system prune -a --volumes

# This removes: stopped containers, unused images, unused volumes, build cache

Container exits immediately

# Check the exit code and logs:
docker ps -a | grep myapp
docker logs myapp

# Run interactively to debug:
docker run -it --entrypoint bash myapp

Frequently Asked Questions

What's the difference between Docker and a virtual machine?

Virtual machines (VMs) emulate complete hardware and run a full OS with their own kernel. Containers share the host kernel and only contain the application and its dependencies. This makes containers start in milliseconds (vs. minutes for VMs) and use significantly less RAM and storage.

Docker vs. Podman — which should I use?

Both run OCI containers. Podman is daemonless (doesn't need a background service), runs rootless by default, and is included in Fedora/RHEL. Docker has better tooling, wider ecosystem support, and Docker Desktop. For most Linux users, Docker is the safe choice. On Fedora/RHEL, Podman is the native option and is compatible with most Docker commands.

How do I update a container to a new image version?

docker pull image:latest          # pull new version
docker stop container_name
docker rm container_name
docker run -d ... image:latest    # recreate with new image

# With Docker Compose:
docker compose pull
docker compose up -d

How do I backup a Docker volume?

# Backup named volume to tar file:
docker run --rm -v myvolume:/data -v $(pwd):/backup ubuntu 
  tar czf /backup/myvolume_backup.tar.gz -C /data .

# Restore:
docker run --rm -v myvolume:/data -v $(pwd):/backup ubuntu 
  tar xzf /backup/myvolume_backup.tar.gz -C /data

Now that Docker is running, you're ready to install containerized applications. Check out our guide on how to install Open WebUI with Docker to get a ChatGPT-style interface for your local LLMs in minutes.


Go up