Docker packages an application with everything it needs, so you can run it on any server with one command. This guide installs Docker Engine from the official repository and runs a real app on it.
1. Install Docker Engine
Do not use the docker.io package from your distribution. It is usually old. Use Docker's own repository.
$ sudo apt-get update $ sudo apt-get install -y ca-certificates curl $ sudo install -m 0755 -d /etc/apt/keyrings $ . /etc/os-release $ echo "$ID $VERSION_CODENAME" $ sudo curl -fsSL https://download.docker.com/linux/$ID/gpg -o /etc/apt/keyrings/docker.asc $ sudo chmod a+r /etc/apt/keyrings/docker.asc $ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $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
The . /etc/os-release line loads your distribution name and codename into the shell, so the same commands work on both Ubuntu and Debian. It prints nothing by itself; the echo after it shows what was loaded, for example ubuntu noble or debian trixie. If that line prints nothing at all, you are not on a Debian-based system, so use the block below instead.
$ sudo dnf -y install dnf-plugins-core $ sudo curl -fsSL https://download.docker.com/linux/centos/docker-ce.repo -o /etc/yum.repos.d/docker-ce.repo $ sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin $ sudo systemctl enable --now docker
The repository says centos, which is correct for AlmaLinux, Rocky Linux, and RHEL, because they are binary compatible. We download the repository file with curl because the dnf config-manager syntax changed between AlmaLinux 9 and AlmaLinux 10, and downloading the file works on both.
Check that it works:
$ sudo docker run --rm hello-world $ docker compose version
Note the command is docker compose with a space. The old docker-compose with a hyphen is version 1 and no longer maintained.
2. Run Docker without sudo (optional)
$ sudo usermod -aG docker $USERLog out and log in again for the change to apply.
The docker group equals root
Any user in the docker group can start a container that mounts the whole filesystem, which means they can become root. Only add users you would trust with the root password. On a server where you already work as one admin user, this is fine. On a shared server, skip this step and keep using sudo.
3. Run your first real app
Here is a monitoring tool, Uptime Kuma, in one command:
$ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:2What the parts mean:
-druns it in the background.--restart=alwaysstarts it again after a reboot or a crash.-p 3001:3001maps port 3001 on the server to port 3001 in the container.-v uptime-kuma:/app/datastores the app data in a Docker volume, outside the container.louislam/uptime-kuma:2is the image and its major version.
Open http://YOUR.SERVER.IP:3001 in a browser to reach it.
4. Volumes keep your data
A container is disposable. When you delete it, everything inside it is gone. Anything you want to keep must live in a volume, which is the -v part above.
$ docker volume ls $ docker ps $ docker logs uptime-kuma $ docker stop uptime-kuma && docker rm uptime-kuma
You can delete and recreate the container with the same -v option, and your data is still there. That is also how you update: pull the new image, remove the container, run it again.
5. Use Compose for anything with more than one part
Long docker run commands are hard to remember. A Compose file stores the same settings in a file you can read and edit:
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: always
ports:
- "3001:3001"
volumes:
- uptime-kuma:/app/data
volumes:
uptime-kuma:$ cd /opt/kuma $ docker compose up -d $ docker compose logs -f $ docker compose pull && docker compose up -d
The last line is how you update: pull new images and recreate the containers. Your volumes are not touched.
6. Do not expose containers to the internet directly
Port 3001 in the example above is open to everyone, over plain HTTP. That is fine for a first test and wrong for anything real. Bind the container to localhost only, then put a reverse proxy with HTTPS in front of it:
ports:
- "127.0.0.1:3001:3001"Our guide on Nginx and Let's Encrypt covers the proxy and the certificate.
Quick reference
| Task | Command |
|---|---|
| List running containers | docker ps |
| See logs | docker logs NAME or docker compose logs -f |
| Stop and remove | docker stop NAME && docker rm NAME |
| Update an image | docker compose pull && docker compose up -d |
| List volumes | docker volume ls |
| Free unused space | docker system prune |
Where to go next
Add a reverse proxy so your app is served over HTTPS on your own domain. Keep an eye on disk usage: Docker images and build cache grow quietly, and our guide on freeing disk space covers the cleanup commands.