Docker has made self-hosting dramatically simpler: what used to cost an evening of installation work now starts from one configuration file and a single command. The installation itself takes ten minutes — provided you use the official repository and know about one nasty trap that catches even experienced admins: Docker ignores your firewall.
What you need
You need a server with root access running Debian or Ubuntu; 2 GB of RAM is enough for your first services. Virtualization matters here: on a true KVM server, Docker runs natively and without restrictions — on container-based VPS products (LXC/OpenVZ) it often works only with contortions, or not at all.
Step 1: Install Docker from the official repository
Distribution packages for Docker are frequently months behind. Install from the official Docker repository instead: it gives you current versions and clean updates through apt. The commands below set it up on Debian (for Ubuntu, replace debian with ubuntu in both URLs):
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
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-compose-plugin
The test container tells you whether everything works:
sudo docker run hello-world
If "Hello from Docker!" appears, your installation is complete — including Docker Compose, which today ships as a plugin (docker compose, no hyphen).
Step 2: Use Docker without sudo (optional)
To avoid prefixing every command with sudo, add your user to the docker group and log in again once:
sudo usermod -aG docker $USER
Be clear about one consequence: members of the docker group can effectively gain root privileges through containers. On a server you administer alone, that is common and acceptable — but group membership for additional users should be treated exactly like handing out root access.
Step 3: Your first real service with Docker Compose
Rather than typing long docker run commands, you describe services in a compose.yaml — reviewable, version-controllable, startable with one command. Uptime Kuma makes a practical first project: a self-hosted monitoring tool with a web interface. Create a folder containing this file:
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- ./data:/app/data
Then, in that same folder:
docker compose up -d
Docker pulls the image and starts the service in the background; docker compose logs -f shows what is happening. Thanks to restart: unless-stopped, the container survives a server reboot too. Why the port above is deliberately bound to 127.0.0.1 is the subject of the next section — the most important one in this article.
The trap: Docker bypasses your firewall
If you have secured your server with UFW, you expect that whatever is not allowed is closed. Docker breaks that expectation. A published port (ports: "3001:3001") is written straight into the iptables rules by Docker, bypassing UFW. The service is then reachable from the internet even though your firewall appears to block it. This is how databases and admin panels end up publicly exposed by accident.
The robust fix is the pattern shown above: bind ports to 127.0.0.1 only. The container then listens locally, and you decide what goes outside — properly, through a reverse proxy with TLS, or for your own use through an SSH tunnel:
ssh -L 3001:127.0.0.1:3001 user@server-ip
You can then reach the interface locally at http://localhost:3001 — encrypted by SSH, invisible to the rest of the internet.
Rule of thumb: publish container ports as
127.0.0.1:PORT:PORTby default. A service becomes public through a deliberate decision of yours — never through a Docker default.
Updates and housekeeping
Updating containers takes two commands: docker compose pull fetches new images, docker compose up -d restarts the services with them. Old images stay behind and fill up the disk over time — docker system prune clears out what is unused (check with docker system df first to see what can go). An occasional look at df -h is part of the routine when you host containers.
Conclusion
The official repository, Compose instead of long individual commands, and ports consistently bound to 127.0.0.1 — with those three habits your Docker setup stays clean, maintainable and safe. After the first service, such a server tends to grow quickly; how much CPU and RAM a multi-container setup needs is covered in the server sizing guide. And if it grows further: the AMD EPYC servers offer exactly the profile parallel containers benefit from, with plenty of cores and NVMe storage.