Disclosure: we may earn a commission or referral reward from CloudCone, Contabo, DigitalOcean (account credit, not cash), Hostwinds, InterServer, RackNerd, VPSDime and Vultr if you sign up through our links. It does not change the price you pay. Every other provider pays us nothing. Prices in this guide are as of the date shown and may have changed; see the comparison pages for the latest checked prices.

Guides · BLOG

How to install Docker on any Linux VPS in 2026

Some prices quoted in this guide are out of date. It is being rebuilt on our price index; for current prices use the comparison pages, which show when each price was checked.

Docker is the fastest way to deploy an app on a VPS in 2026. This guide walks through the correct installation on the four most common VPS distros (Ubuntu, Debian, CentOS, Rocky), followed by the essential first configuration.

For Ubuntu / Debian (10-minute install)

```bash

Remove any old Docker installations

apt remove docker docker-engine docker.io containerd runc 2>/dev/null || true

Install prerequisites

apt update

apt install ca-certificates curl gnupg lsb-release

Add Docker's official GPG key

mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \

gpg --dearmor -o /etc/apt/keyrings/docker.gpg

Add the repository (Ubuntu-specific; for Debian, replace 'ubuntu' with 'debian')

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \

https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \

tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker and Compose plugin

apt update

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

```

Verify:

```bash

docker run hello-world

```

You should see the "Hello from Docker!" message.

For CentOS / Rocky / RHEL

```bash

Remove old versions

yum remove docker docker-client docker-client-latest docker-common docker-latest \

docker-latest-logrotate docker-logrotate docker-engine 2>/dev/null || true

Install Docker repo

yum install -y yum-utils

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

Install Docker

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

Start and enable

systemctl enable --now docker

```

Add a non-root user to the docker group

Running docker commands as root every time is dangerous. Add a non-root user:

```bash

usermod -aG docker deploy

```

Log out and back in for the group change to take effect. Now deploy can run docker without sudo.

Security note: any user in the docker group has effective root on the host (via docker run --privileged -v /:/host ...). Only add trusted users.

Configure Docker daemon

Create /etc/docker/daemon.json:

```json

{

"log-driver": "json-file",

"log-opts": {

"max-size": "10m",

"max-file": "3"

},

"default-address-pools": [

{ "base": "172.17.0.0/16", "size": 24 }

]

}

```

Restart Docker:

```bash

systemctl restart docker

```

This limits log rotation (default is unlimited — logs will eat your disk).

Install docker-compose (already covered by Compose plugin)

The Docker installation above includes the docker compose plugin (note: docker compose, not docker-compose).

Test:

```bash

docker compose version

```

Should return the version.

For the older standalone docker-compose command (if some tutorial requires it):

```bash

apt install docker-compose # Debian/Ubuntu

or

pip install docker-compose # cross-platform

```

Configure the firewall

Docker punches through UFW/iptables by default. This is by design but confusing. If you want Docker's exposed ports to respect UFW:

```bash

Add to /etc/ufw/after.rules at the bottom:

*filter

:DOCKER-USER - [0:0]

:ufw-user-input - [0:0]

-A DOCKER-USER -j ufw-user-input

-A DOCKER-USER -j RETURN

COMMIT

```

Restart UFW: ufw reload.

Now Docker's exposed ports go through UFW.

Essential first commands

```bash

List running containers

docker ps

List all containers (including stopped)

docker ps -a

List images

docker images

Remove unused stuff (frees a lot of disk)

docker system prune -af --volumes

Get a shell in a running container

docker exec -it CONTAINER_NAME bash

View container logs

docker logs -f CONTAINER_NAME

Check disk usage by Docker

docker system df

```

Disk cleanup cron

Docker eats disk over time (unused images, stale layers, stopped containers). Set a weekly cleanup:

```bash

crontab -e

Add:

0 4 0 docker system prune -af --volumes > /var/log/docker-prune.log 2>&1

```

Sunday 4am cleanup keeps your disk sane.

Deploy a first app

Create a docker-compose.yml:

```yaml

services:

web:

image: nginx:latest

restart: unless-stopped

ports:

  • "80:80"

volumes:

  • ./html:/usr/share/nginx/html:ro

```

```bash

mkdir html

echo "Hello Docker!" > html/index.html

docker compose up -d

```

Visit http://your-vps-ip/ — you should see "Hello Docker!".

Common gotchas

"Cannot connect to the Docker daemon"

  • Docker daemon not running: systemctl start docker.
  • Or you forgot to log out and back in after adding your user to the docker group.

"Bind for 0.0.0.0:80 failed: port is already allocated"

  • Something else (Nginx, Apache) is using port 80. Stop it or use a different port.

Docker takes all my disk.

  • Run docker system prune -af --volumes. Set the weekly cron above.

"exec /bin/sh: exec format error"

  • You're on ARM64 and pulled an x86_64 image (or vice versa). Use docker pull --platform linux/amd64 image for the right arch.

What NOT to run in Docker on a small VPS

  • A database as a "temporary" container. Bind mount /var/lib/mysql or /var/lib/postgres to a volume so data persists.
  • --privileged containers you don't understand. Effective root on the host.
  • Random Docker images from Docker Hub without checking the source. Supply chain attacks are real.

Rootless Docker: worth it?

Docker's rootless mode runs the daemon as an unprivileged user. Better security posture at the cost of some feature limitations (port binding under 1024 requires setcap or a reverse proxy). For a single-tenant VPS running trusted workloads, rootless is nice-to-have not need-to-have. For a shared VPS or a workload running untrusted third-party code, rootless is worth the setup complexity. Full instructions at docs.docker.com/engine/security/rootless/.

Podman as an alternative

Red Hat's Podman is a drop-in replacement for Docker with a compatible CLI (alias docker=podman works). Rootless by default. Preferred on Fedora/RHEL/Rocky by some operators for the security posture. On Ubuntu/Debian, Docker is still the default choice and community expects it.

Memory considerations

Docker's daemon uses ~50-100 MB of RAM baseline. Each running container adds its own footprint. On a 1 GB VPS, the overhead is meaningful; on a 4 GB Hetzner CX22 it's noise. If your workload is small (a Discord bot, a single small web service), running natively without Docker saves you the daemon overhead.

Image size discipline

Docker images grow. A naive Node.js image can be 1 GB; a well-optimised one is 100 MB. Multi-stage builds and Alpine/distroless base images reduce size dramatically:

```dockerfile

Bad: 1 GB

FROM node:20

COPY . .

RUN npm install

CMD ["node", "index.js"]

Better: 200 MB

FROM node:20-alpine

COPY package*.json ./

RUN npm ci --production

COPY . .

CMD ["node", "index.js"]

Best: 100 MB with multi-stage

FROM node:20-alpine as builder

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM node:20-alpine

COPY --from=builder /dist ./dist

COPY --from=builder /node_modules ./node_modules

CMD ["node", "dist/index.js"]

```

Smaller images pull faster, use less disk, and deploy faster on redeploys.

Related guides