Running Docker Containers on elementary OS
Docker Engine provides a practical way to run applications in isolated containers without installing every dependency directly on your elementary OS system. A container packages an application and its libraries, while sharing the host kernel and using considerably fewer resources than a full virtual machine.
For elementary OS users, Docker is useful for local web development, self-hosted services, databases, automation tools and testing. You can try a new application without filling the host system with configuration files, and remove the container when the experiment is finished.
The process is similar to installing Docker on Ubuntu because elementary OS is built on Ubuntu. The exact Ubuntu base release still matters, though. elementary OS 7 uses Ubuntu 22.04 technology, while elementary OS 8 uses Ubuntu 24.04 technology, so the Docker package repository must use the matching codename.
This guide uses Docker Engine and the Docker Compose plugin rather than Docker Desktop. That approach is well suited to Linux laptops and desktops, including systems connected through Australian NBN services or hosted on a small home server in Sydney, Melbourne, Brisbane or Perth.
Check Your Elementary OS Base
Open Terminal and inspect the release information before adding any software repository:
cat /etc/os-release
Look for VERSION_CODENAME or UBUNTU_CODENAME. You should see a supported Ubuntu codename such as jammy or noble. Docker’s packages are built for Ubuntu releases, so an elementary-specific codename should not be used in the repository address.
You can save the detected value in a shell variable:
CODENAME=$(. /etc/os-release; echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
echo "$CODENAME"
If this prints jammy or noble, it is generally suitable for the matching elementary OS release. If it prints nothing or displays an elementary-specific value, check the Ubuntu base version in the elementary OS release notes and set the variable manually. For example:
CODENAME=jammy
Do not use a newer Ubuntu codename simply because it is available. Mixing repositories from different releases can produce dependency conflicts, especially after a routine system update. Make sure your system is current first:
sudo apt update
sudo apt upgrade
Add Docker’s Official Package Repository
Install the tools required to download repository metadata securely:
sudo apt install ca-certificates curl
Create a directory for the Docker signing key and download the keyring:
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
Now add Docker’s Ubuntu repository. The command below uses the codename detected earlier and your computer’s processor architecture:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$CODENAME stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the package index and check which Docker Engine version is available:
sudo apt update
apt-cache policy docker-ce
The output should show a candidate package from download.docker.com. If it reports that the repository has no Release file, stop and recheck the codename. On an elementary OS installation that has been upgraded across major releases, stale repository files can also cause this error.
The official repository is preferable to an old distribution package because it provides current versions of the Engine, command-line client, containerd runtime and Compose integration. It also avoids relying on a random installation script, which makes future package management clearer.
Install Docker Engine And Compose
Install the main Docker components with:
sudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
These packages provide the daemon, command-line interface, low-level container runtime, BuildKit-based image builder and the modern Compose command. The Compose syntax is now invoked as docker compose, with a space, rather than the older standalone docker-compose command.
Confirm that the Docker service is running:
sudo systemctl status docker
Press q to leave the status screen. If the service is not active, start it and configure it to launch during boot:
sudo systemctl enable --now docker
Run Docker’s small verification image:
sudo docker run hello-world
Docker will download the hello-world image from Docker Hub, create a container and print a confirmation message. The first download may take a little longer on a busy NBN connection, while subsequent runs use the local image cache.
You can see the installed versions with:
docker --version
docker compose version
At this stage, Docker works through sudo. That is a useful initial test because it separates installation problems from user-permission problems.
Run Docker Without Sudo
By default, access to the Docker daemon is restricted to root. Add your everyday account to the docker group if you want to run container commands without typing sudo:
sudo usermod -aG docker "$USER"
The group change applies after you sign out and sign back in. You can also activate it for the current terminal session with:
newgrp docker
Test the new permission:
docker run hello-world
Membership in the Docker group effectively grants root-level control over the host. A container with access to sensitive directories, devices or the Docker socket can potentially affect the entire operating system. Treat Docker group membership like administrator access, particularly on a shared family computer, a work laptop or a machine used for TAFE or university projects.
For a single short-lived command, keeping sudo may be more appropriate. Check the current account and group membership with:
id
If the docker group is listed but the command still returns a permission error, close all desktop sessions and log in again. On elementary OS, this may mean signing out through the Applications menu rather than simply closing Terminal.
Launch A Test Web Container
A web server is a useful way to understand images, containers, ports and background processes. Start an Nginx container using the lightweight Alpine variant:
docker run -d \
--name elementary-web \
--publish 8080:80 \
nginx:alpine
The -d option runs the container in the background. --name gives it a memorable name, while --publish 8080:80 maps port 8080 on elementary OS to port 80 inside the container.
Open http://localhost:8080 in a browser. You should see the Nginx welcome page. This service remains available only on your computer unless you deliberately expose it through your network or router. That is a safer default than making a development service reachable from the wider internet.
Inspect the running container:
docker ps
docker logs elementary-web
Stop and remove it when finished:
docker stop elementary-web
docker rm elementary-web
The image remains on disk for later use. View downloaded images with:
docker image ls
Remove the test image if you no longer need it:
docker image rm nginx:alpine
A port conflict occurs if another application already uses 8080. Choose another host port, such as 8081:80, and browse to http://localhost:8081. This is common when local development tools, media servers or another web stack are already running.
Manage Projects With Docker Compose
Docker Compose describes a multi-container project in a YAML file. It is useful when an application needs a database, cache or separate worker alongside its web service. Create a directory for a small demonstration project:
mkdir -p ~/docker/nginx-demo
cd ~/docker/nginx-demo
Create compose.yaml:
services:
web:
image: nginx:alpine
container_name: compose-web
ports:
- "8080:80"
restart: unless-stopped
Start the project in the background:
docker compose up -d
Check its status:
docker compose ps
The service should be available at http://localhost:8080. View recent output with:
docker compose logs --follow
Press Ctrl+C to stop following the logs without stopping the service. When you want to remove the containers created by this project, run:
docker compose down
Compose files are ordinary text, so they fit neatly into Git repositories and backups. Keep passwords and API keys out of the file when possible; use an .env file with suitable permissions or a dedicated secrets mechanism for sensitive projects. A Compose stack for a local WordPress site, for example, may include both WordPress and MariaDB, with persistent volumes so database contents survive container replacement.
Containers are disposable, but data should not be. Named volumes are managed by Docker and can be listed with:
docker volume ls
Bind mounts, such as ./site:/usr/share/nginx/html, let a container serve files directly from your home directory. Review permissions carefully before mounting a whole home folder, and avoid mounting system directories into untrusted images.
Update, Troubleshoot And Operate Safely
Docker has several separate components, so useful diagnostics begin with the service and client:
systemctl is-active docker
docker info
docker ps -a
If Docker fails to start, inspect recent service messages:
journalctl -u docker --no-pager -n 50
A “permission denied” message usually means the current login has not picked up membership in the Docker group. A “port is already allocated” message means another process or container owns the requested host port. Find stopped containers with docker ps -a, then remove an unused one or select a different port.
Keep images and containers updated deliberately. Pull a newer image and recreate a Compose service with:
docker compose pull
docker compose up -d
For a manually started container, record the original docker run options or use Compose from the beginning. Docker does not automatically replace a running container merely because a newer image exists.
Unused images, stopped containers and build cache can consume substantial storage on a laptop with a small SSD. Review usage before cleaning:
docker system df
A cautious cleanup removes stopped containers, unused networks and dangling images:
docker system prune
Do not add --volumes casually, because it can remove data volumes that are no longer attached to a running container but are still important. On a home server in Perth or regional New South Wales, also consider power interruptions and backup schedules; persistent application data should be copied to separate storage rather than left only in Docker’s data directory.
Avoid publishing administration panels directly to the internet. A service bound to 127.0.0.1 is local-only, while 0.0.0.0 can make it reachable on every network interface. If remote access is required, use authentication, firewall rules, TLS and a maintained reverse proxy. Australian home routers often sit behind changing residential addresses, so exposing a port through the router should be a deliberate security decision rather than an accidental consequence of a Compose file.
Docker Engine is now installed, the service starts with the system, and the Compose plugin can run repeatable projects. Keep your first applications local, use named volumes for data that matters, and record each project’s Compose file alongside its configuration. The next concrete step is to create ~/docker/nginx-demo/compose.yaml, run docker compose up -d, and open http://localhost:8080 in your browser.