Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Docker Ubuntu
GUIDEInstall Guides

How to Install Docker on Ubuntu 24.04 — Production-Ready Container Runtime

26 min read

How to Install Docker on Ubuntu 24.04 — Production-Ready Container Runtime

Docker turned containers from a kernel feature into a workflow. With a single docker run you pull an image, spin up a sandboxed process, wire up networking and volumes, and tear it down again — with none of the drift, dependency hell, or "works on my machine" pain of traditional installs. On a fresh Ubuntu 24.04 VPS, a properly installed Docker engine becomes the foundation for everything from a single self-hosted app to a full microservices stack orchestrated by Docker Compose or managed through Portainer.

This guide takes you from a bare Ubuntu 24.04 server to a hardened, production-tuned Docker installation. You will remove the older docker.io snapshot that ships in the Ubuntu archive, add Docker's official apt repository with its GPG key, install docker-ce alongside the Compose and Buildx plugins, apply the post-install recommendations, tune daemon.json for live-restore and disciplined logging, optionally enable rootless mode, and finish with a pruning policy that keeps /var/lib/docker from eating your disk.

Prefer to roll your own runtime rather than pay for a managed container platform? Our Starter VPS gives you full root on bare metal–grade KVM hardware from EUR 7.99/month — run Docker, Compose and Portainer without the per-container premium of ECS or Cloud Run.

Table of Contents

  • Why Self-Host Docker on a VPS?
  • Prerequisites
  • Step 1: Update the System
  • Step 2: Remove Older Docker Packages
  • Step 3: Add Docker's Official Apt Repository
  • Step 4: Install Docker Engine, CLI and Plugins
  • Step 5: Post-Install — docker Group and usermod
  • Step 6: Configure daemon.json for Production
  • Step 7: Enable Rootless Mode (Optional)
  • Step 8: Docker Networking — bridge vs host
  • Step 9: Install Portainer (Optional GUI)
  • Step 10: Set Up a Pruning Policy
  • Troubleshooting
  • FAQ
  • Next Steps
  • Why Self-Host Docker on a VPS?

    Managed container platforms — AWS ECS, Google Cloud Run, Azure Container Apps, Fly.io, Railway — are excellent at hiding the runtime. You push an image, they schedule it. The trade-off is cost, egress, lock-in, and a surprisingly thin layer of control.

    Running Docker yourself on a VPS gives you:

    • Flat, predictable pricing — One monthly VPS fee covers unlimited containers, unlimited builds, unlimited pulls and unlimited internal traffic. Managed platforms charge per vCPU-second, per GB-second of memory, per request, and per GB of egress. A stack that costs EUR 7/month on a Starter VPS regularly bills at USD 40-80 on Cloud Run.
    • No cold starts — Serverless container runtimes spin down idle instances and add 200-2000 ms latency on the next request. A long-running dockerd keeps your containers hot 24/7.
    • Full kernel and networking access — You can use --network host, raw sockets, privileged containers, custom iptables rules, WireGuard sidecars, and kernel modules. Managed platforms block most of these.
    • Runtime choice — You pick the containerd version, the logging driver, the cgroup driver, the storage driver, the address pools, and whether to enable BuildKit or Buildx by default.
    • One tool, everywhere — The same docker compose up -d works on your laptop, your CI runner, your staging VPS and your production VPS. Managed platforms each have their own deployment model.
    • Data locality — Volumes live on your disk. Backups are rsync or restic snapshots. No object storage API calls, no egress bills when you restore.
    • Zero per-seat billing — Team members get SSH access to the same host. No users × environments × regions multiplier.
    For a solo developer, a small agency, or a SaaS team running 1-50 containers, a VPS with Docker is often 5-10x cheaper than the equivalent managed service — and you keep the operational skills that translate directly to Kubernetes when you actually need it.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS (Noble Numbat) with root or sudo access
    • SSH access to your server
    • At least 2 GB RAM and 20 GB of free disk for the daemon, images and a handful of containers (4 GB / 40 GB recommended)
    • A non-root user with sudo privileges (the examples below assume the user admin)
    Recommended Plan: Starter VPS
    >
    For a typical Docker host running 3-8 containers — reverse proxy, database, cache, one or two web apps — we recommend the Starter VPS plan:
    >
    - 4 vCPU cores
    - 8 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - From EUR 7.99/month
    >
    Need more headroom for databases or a dozen-plus containers? Step up to our Professional or Performance plans on the same page.

    Connect to your server via SSH:

    bash
    ssh admin@your-server-ip

    Step 1: Update the System

    Refresh the package index and apply any pending security updates before installing a new repository.

    bash
    sudo apt update && sudo apt upgrade -y

    Expected output (abbreviated):

    text
    Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
    Hit:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease
    Reading package lists... Done
    Building dependency tree... Done
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

    If the kernel was updated, reboot and reconnect:

    bash
    sudo reboot

    Install a few helpers that the Docker apt setup depends on:

    bash
    sudo apt install -y ca-certificates curl gnupg lsb-release

    Step 2: Remove Older Docker Packages

    Ubuntu's archive ships docker.io, docker-doc, docker-compose (v1, Python), podman-docker, and older containerd/runc packages. These conflict with the official Docker CE packages and — worse — often get preferred by apt's dependency solver if they stay installed. Purge them first.

    bash
    for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
      sudo apt-get remove -y $pkg
    done

    Expected output (when nothing is installed, which is normal on a fresh VPS):

    text
    Package 'docker.io' is not installed, so not removed
    Package 'docker-doc' is not installed, so not removed
    ...

    If any of those packages were installed and you want to also wipe their config and data, run:

    bash
    sudo apt purge -y docker.io docker-compose runc containerd
    sudo rm -rf /var/lib/docker /var/lib/containerd
    Careful: rm -rf /var/lib/docker destroys every image, container and named volume on the host. Only run it on a fresh system or after you have backed up your volumes.

    Step 3: Add Docker's Official Apt Repository

    Docker signs its apt repository with a GPG key hosted at download.docker.com. Install the key into /etc/apt/keyrings — the modern location that avoids the deprecated apt-key tool — then add a signed-by repo entry so apt verifies every package against it.

    Create the keyrings directory and download the GPG key:

    bash
    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 repository. The command below resolves your Ubuntu codename (noble on 24.04) and architecture (amd64, arm64, etc.) automatically:

    bash
    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

    Refresh the package index so apt picks up the new source:

    bash
    sudo apt update

    Expected output:

    text
    Get:5 https://download.docker.com/linux/ubuntu noble InRelease [48.8 kB]
    Get:6 https://download.docker.com/linux/ubuntu noble/stable amd64 Packages [15.4 kB]
    Reading package lists... Done

    If you see NO_PUBKEY or signatures couldn't be verified, the GPG key download failed — rerun the curl command above and double-check that /etc/apt/keyrings/docker.asc exists and is readable.

    Step 4: Install Docker Engine, CLI and Plugins

    With the repository wired up, install the full Docker CE stack:

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

    What each package provides:

    • docker-ce — the Docker daemon (dockerd) and the systemd unit
    • docker-ce-cli — the docker CLI you run on the command line
    • containerd.io — the lower-level container runtime that dockerd delegates to
    • docker-buildx-plugin — multi-platform builds via BuildKit (docker buildx build)
    • docker-compose-plugin — Compose v2 as a subcommand (docker compose up, no hyphen)
    Confirm the engine and CLI versions:

    bash
    docker --version
    docker compose version
    docker buildx version

    Expected output:

    text
    Docker version 27.5.1, build 9f9e405
    Docker Compose version v2.32.4
    github.com/docker/buildx v0.19.3

    Check that the daemon is running:

    bash
    sudo systemctl status docker

    Expected output:

    text
    ● docker.service - Docker Application Container Engine
         Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled)
         Active: active (running) since Thu 2026-04-16 10:12:03 UTC; 30s ago

    Run the canonical smoke test:

    bash
    sudo docker run --rm hello-world

    Expected output:

    text
    Hello from Docker!
    This message shows that your installation appears to be working correctly.

    Step 5: Post-Install — docker Group and usermod

    Out of the box, only root can talk to /var/run/docker.sock. Prefixing every command with sudo works but clutters your history and defeats shell completion. The fix is to add your user to the docker group.

    Security note: Membership in the docker group is effectively equivalent to root on the host. Any user who can talk to the Docker socket can mount / into a privileged container and rewrite any file. Only grant it to trusted administrators. If that worries you, skip to Step 7 and use rootless Docker instead.

    Create the group (the package install may already have created it — groupadd with || true makes the command idempotent):

    bash
    sudo groupadd docker || true

    Add your current user:

    bash
    sudo usermod -aG docker $USER

    Apply the new group membership without logging out:

    bash
    newgrp docker

    Verify by running docker without sudo:

    bash
    docker run --rm hello-world

    If you see a permission denied while trying to connect to the Docker daemon socket error, log out of your SSH session completely and reconnect — newgrp only affects the current shell.

    Finally, make sure Docker starts on boot (this is the default, but worth confirming on older images):

    bash
    sudo systemctl enable --now docker.service containerd.service

    Step 6: Configure daemon.json for Production

    Out of the box, dockerd uses the json-file log driver with no size limit — meaning a chatty container can silently fill /var/lib/docker/containers//.log until the disk is full. It also restarts all containers on every systemctl restart docker, which is disruptive. A good production daemon.json fixes both.

    Create /etc/docker/daemon.json:

    bash
    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
    {
      "live-restore": true,
      "log-driver": "json-file",
      "log-opts": {
        "max-size": "10m",
        "max-file": "5",
        "compress": "true"
      },
      "default-address-pools": [
        { "base": "10.200.0.0/16", "size": 24 },
        { "base": "10.201.0.0/16", "size": 24 }
      ],
      "default-ulimits": {
        "nofile": { "Name": "nofile", "Hard": 65535, "Soft": 65535 }
      },
      "no-new-privileges": true,
      "userland-proxy": false,
      "features": { "buildkit": true },
      "metrics-addr": "127.0.0.1:9323"
    }
    EOF

    What each option does:

    • live-restore: true — Keeps running containers alive when the daemon is restarted or upgraded. Without this, every apt upgrade docker-ce takes every container offline.
    • log-driver: json-file + log-opts — Caps per-container log files at 10 MB and keeps 5 rotations (so 50 MB max per container). compress: true gzips rotated files. You can swap this for journald, syslog or loki later.
    • default-address-pools — Docker carves subnets for user-defined bridge networks from these pools. The default (172.17.0.0/16 and 172.18.0.0/16) collides with many VPN and corporate networks. Overriding to 10.200.0.0/16 avoids that pain.
    • default-ulimits.nofile — Raises the open-file limit inside containers from 1024 to 65535 — important for databases, reverse proxies and anything handling thousands of connections.
    • no-new-privileges: true — Prevents processes inside containers from gaining new privileges via setuid binaries. A free hardening win.
    • userland-proxy: false — Uses iptables DNAT for published ports instead of the userland docker-proxy helper. Lower CPU overhead on high-throughput workloads.
    • features.buildkit: true — Makes BuildKit the default builder for docker build. Faster, parallel, with better caching.
    • metrics-addr: 127.0.0.1:9323 — Exposes Prometheus-format daemon metrics on localhost for scraping.
    Validate the JSON and reload the daemon:

    bash
    sudo dockerd --validate
    sudo systemctl restart docker
    sudo systemctl status docker --no-pager

    Confirm the address pools took effect:

    bash
    docker network create test-net
    docker network inspect test-net | grep Subnet
    docker network rm test-net

    Expected output:

    text
    "Subnet": "10.200.0.0/24",

    Step 7: Enable Rootless Mode (Optional)

    Rootless Docker runs the daemon as an unprivileged user inside a user namespace. A container escape cannot pivot to host root because the "root" inside the container maps to your unprivileged UID on the host. The trade-offs: slightly slower networking via slirp4netns, no binding to ports below 1024 without extra capabilities, and a few storage drivers are unavailable.

    Install the rootless helpers and uidmap utilities:

    bash
    sudo apt install -y docker-ce-rootless-extras uidmap dbus-user-session fuse-overlayfs

    Stop the system-wide daemon (optional — you can keep both, running on different sockets):

    bash
    sudo systemctl disable --now docker.service docker.socket

    Run the setup tool as your regular user (not with sudo):

    bash
    dockerd-rootless-setuptool.sh install

    Expected output:

    text
    [INFO] Creating /home/admin/.config/systemd/user/docker.service
    [INFO] starting systemd service docker.service
    [INFO] Installed docker.service successfully.
    [INFO] To control docker.service, run: systemctl --user (start|stop|restart) docker.service
    [INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):
    export PATH=/usr/bin:$PATH
    export DOCKER_HOST=unix:///run/user/1000/docker.sock

    Add the exports to your shell profile:

    bash
    echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
    source ~/.bashrc

    Enable lingering so the user daemon survives logout:

    bash
    sudo loginctl enable-linger $USER

    Verify rootless operation:

    bash
    docker info | grep -i rootless

    Expected output:

    text
    Security Options:
      rootless

    For a deeper comparison of rootless runtimes, see our guide on Podman vs Docker.

    Step 8: Docker Networking — bridge vs host

    Docker ships with three built-in network drivers that cover 99% of single-host workloads:

    bridge — the default, isolated

    Every container gets its own IP on an internal Linux bridge (docker0 by default, or a user-defined bridge). Ports you want reachable from outside must be explicitly published with -p.

    bash
    # User-defined bridge with DNS-based container name resolution
    docker network create app-net
    docker run -d --name web --network app-net -p 8080:80 nginx
    docker run -d --name api --network app-net my-api:latest
    

    From inside the api container, curl http://web just works

    User-defined bridges (not the legacy docker0) give you automatic DNS for container names, better isolation and the ability to connect/disconnect on the fly. Use a user-defined bridge for every multi-container app.

    host — no isolation, maximum performance

    The container shares the host's network namespace. No -p flag, no NAT, no iptables hop. The container binds directly to host ports.

    bash
    docker run -d --network host nginx
    

    nginx is now listening on the host's :80, exactly as if you'd apt installed it

    Use host networking for ultra-low-latency workloads (game servers, DNS servers, load balancers handling millions of connections), WireGuard, or anything that needs to see the real client IP without reverse-proxy shenanigans. The downside is zero isolation between the container and the host network, and you lose Docker's port-publishing abstraction.

    none — fully isolated

    No network at all. Useful for batch jobs that must not reach the internet.

    bash
    docker run --rm --network none alpine ip addr
    

    Only lo is present

    For multi-host networking, the built-in overlay driver works with Swarm mode, but if you are at that scale you are probably better served by Kubernetes.

    Inspect your networks:

    bash
    docker network ls

    Expected output:

    text
    NETWORK ID     NAME      DRIVER    SCOPE
    a1b2c3d4e5f6   bridge    bridge    local
    7g8h9i0j1k2l   host      host      local
    3m4n5o6p7q8r   none      null      local

    Step 9: Install Portainer (Optional GUI)

    Portainer gives you a web UI for managing containers, images, volumes, networks and Compose stacks — useful if you prefer clicking over SSHing, or if non-developers on your team need read-only visibility.

    Create a persistent volume and launch the community edition:

    bash
    docker volume create portainer_data

    docker run -d \ --name portainer \ --restart=always \ -p 9443:9443 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:latest

    Open https://your-server-ip:9443 in a browser (the CE image generates a self-signed cert on first boot), create the admin user within five minutes of starting the container, and pick "Docker Socket" as the environment to manage.

    For a walkthrough on hardening the Portainer install behind a reverse proxy with a real TLS certificate, see our dedicated guide on installing Portainer.

    Step 10: Set Up a Pruning Policy

    Docker's append-only model means /var/lib/docker grows forever unless you clean it. Stopped containers, dangling images, unused volumes and build cache accumulate quickly on a busy CI host.

    Manual pruning

    Inspect what Docker is using:

    bash
    docker system df

    Expected output:

    text
    TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
    Images          18        5         3.2GB     2.1GB (65%)
    Containers      7         4         112MB     43MB (38%)
    Local Volumes   9         6         845MB     210MB (24%)
    Build Cache     41        0         1.4GB     1.4GB

    Aggressive one-shot cleanup — removes stopped containers, unused networks, dangling images and build cache older than a week:

    bash
    docker system prune -af --filter "until=168h"

    Drop unused volumes separately (volumes are never auto-pruned by docker system prune because they usually hold data):

    bash
    docker volume ls -qf dangling=true | xargs -r docker volume rm

    Automated weekly pruning

    Create a systemd service and timer so you never have to think about it.

    bash
    sudo tee /etc/systemd/system/docker-prune.service > /dev/null <<'EOF'
    [Unit]
    Description=Weekly Docker prune
    After=docker.service
    Requires=docker.service

    [Service] Type=oneshot ExecStart=/usr/bin/docker system prune -af --filter "until=168h" ExecStart=/bin/sh -c "docker volume ls -qf dangling=true | xargs -r docker volume rm" EOF

    bash
    sudo tee /etc/systemd/system/docker-prune.timer > /dev/null <<'EOF'
    [Unit]
    Description=Run docker-prune weekly

    [Timer] OnCalendar=Sun 03:00 Persistent=true

    [Install] WantedBy=timers.target EOF

    Enable the timer:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable --now docker-prune.timer
    sudo systemctl list-timers docker-prune.timer

    Expected output:

    text
    NEXT                         LEFT          LAST  PASSED  UNIT                ACTIVATES
    Sun 2026-04-19 03:00:00 UTC  2 days left   -     -       docker-prune.timer  docker-prune.service

    A sensible policy for most VPS hosts: prune weekly, keep images used in the last 7 days, drop dangling volumes, and alert only if docker system df shows reclaimable space above 10 GB.

    Troubleshooting

    ProblemCauseSolution
    permission denied while trying to connect to the Docker daemon socketYour user is not in the docker group, or the group change has not taken effect in this shellRun sudo usermod -aG docker $USER, then log out and back in — or run newgrp docker
    Cannot connect to the Docker daemon at unix:///var/run/docker.sockDaemon is not runningCheck with sudo systemctl status docker and start with sudo systemctl start docker; check logs via sudo journalctl -u docker -n 100 --no-pager
    could not find an available, non-overlapping IPv4 address poolDefault Docker pools conflict with a VPN or host routeSet default-address-pools in /etc/docker/daemon.json to a free range like 10.200.0.0/16
    pull access denied for <image>, repository does not exist or may require 'docker login'Typo in image name, private repo, or rate limit from an anonymous Docker Hub pullVerify the image name; run docker login for private registries; consider a mirror or registry proxy if you hit Hub's anonymous pull limits
    hello-world runs but iptables rules are missingDaemon started before nftables/iptables backend was chosenOn 24.04 ensure iptables is in legacy mode: sudo update-alternatives --set iptables /usr/sbin/iptables-legacy, then restart docker
    Disk filling up with no obvious cause/var/lib/docker accumulating logs, images, build cacheRun docker system df to see the breakdown, then docker system prune -af --filter "until=168h" and enable the weekly timer from Step 10
    Rootless daemon fails to start with newuidmap: write to uid_map failedMissing uidmap package or /etc/subuid / /etc/subgid not configured for your userInstall uidmap and verify your user has ranges: grep $USER /etc/subuid /etc/subgid
    Always start debugging with the daemon log:

    bash
    sudo journalctl -u docker -f

    And the per-container log:

    bash
    docker logs -f --tail 200 <container>

    FAQ

    Should I install docker.io from Ubuntu or Docker CE from docker.com?

    Always install Docker CE from docker.com on a server. The docker.io package in the Ubuntu archive is a community snapshot that typically lags upstream by 3-9 months, ships without the Compose v2 and Buildx plugins, and has no rootless tooling. When a CVE lands in containerd or runc, the upstream Docker CE repo gets the patched build on day one while Ubuntu can take weeks. For development laptops docker.io is fine; for a VPS hosting real workloads, Docker CE is the only reasonable choice.

    Do I need to reboot after installing Docker?

    No. The Docker engine starts immediately after apt install and is ready to run containers. You only need to log out and back in (or run newgrp docker) after adding your user to the docker group — and that's a shell-level requirement, not a reboot. The sole exception is if you also upgraded the kernel during Step 1; new cgroup or overlay features may only take effect after a reboot.

    What is the difference between rootless Docker and the default daemon?

    The default daemon runs as root. Anyone in the docker group can mount the host root filesystem into a container and effectively has root access. Rootless Docker runs dockerd as an unprivileged user inside a user namespace — a container escape maps back to your unprivileged UID, not host root. The costs: slirp4netns networking is slightly slower than the bridge driver, binding to ports below 1024 needs extra capabilities, and some storage drivers (like devicemapper) are unavailable. For multi-tenant CI runners or anything running untrusted code, rootless is worth it. For a single-developer VPS, the default daemon plus a firewall is usually enough.

    Docker vs Podman — which should I pick on Ubuntu 24.04?

    Docker has the larger ecosystem, first-party Compose v2, BuildKit/Buildx, the widest third-party tooling, and is the format every CI/CD product understands. Podman is daemonless and rootless-by-default, has a Docker-compatible CLI, and groups containers into pods without needing Kubernetes. If you are deploying Compose stacks, using Portainer, or running anything that expects /var/run/docker.sock, pick Docker. Pick Podman when you need strict rootless semantics, no long-running daemon, or pod-style grouping and you control the whole toolchain.

    How much RAM do I really need to run Docker on a VPS?

    The Docker daemon itself idles at around 100-150 MB of RAM. Real memory usage is entirely driven by your containers. A minimal stack — Caddy or Nginx, a Postgres database, a single Node or Python app, maybe Redis — fits comfortably in 2 GB. Add a Grafana/Prometheus observability stack and you want 4 GB. Heavier workloads (Elasticsearch, Mautic, Jitsi, GitLab) push you to 8-16 GB. Disk is the other dimension: plan for 20 GB minimum and watch /var/lib/docker over time.

    How do I clean up unused images and volumes safely?

    The one-liner for a weekly sweep is docker system prune -af --filter "until=168h" — this removes stopped containers, unused networks, dangling images and build cache older than seven days. It does not touch named volumes, which almost always hold data you care about. For volumes, use docker volume ls -qf dangling=true | xargs -r docker volume rm — this only removes volumes not currently attached to any container. The pruning policy in Step 10 wraps both into a systemd timer that runs every Sunday at 03:00.

    Why does Docker fail with "could not find an available, non-overlapping IPv4 address pool"?

    Docker allocates subnets for user-defined networks from its default-address-pools (out of the box, 172.17.0.0/16 and 172.18.0.0/16). If your host already has a route for that range — a corporate VPN, a WireGuard tunnel, a provider's private-network interface — Docker refuses to create overlapping networks. The fix is to move Docker's pools somewhere safe. Add "default-address-pools": [{"base": "10.200.0.0/16", "size": 24}] to /etc/docker/daemon.json and restart the daemon. You get the same behaviour, just on a range that does not collide.

    Next Steps

    Now that Docker is installed and hardened, here is how to build on it:

    • Learn Docker Compose v2 — Our guide on installing and using Docker Compose covers multi-service stacks, .env files, profiles, healthchecks, and production deploys via docker compose up -d --remove-orphans.
    • Put a UI on it — Install Portainer to manage containers, stacks and volumes through a browser, with role-based access for your team.
    • Consider Podman for untrusted workloads — If you are running third-party or user-submitted code, our Podman vs Docker comparison shows when the daemonless, rootless-by-default design pays off.
    • Graduate to Kubernetes when the time comes — Once you have a dozen servers or need true multi-host orchestration, read our introduction to Kubernetes on a VPS to see what changes and what carries over.
    • Follow the upstream docs — Docker's official documentation at docs.docker.com is the canonical reference for the engine, CLI, Compose, BuildKit and API.

    Need a VPS that's ready for Docker today?
    >
    Our Starter VPS gives you full root on NVMe-backed KVM hardware from EUR 7.99/month — perfect for Docker Compose stacks, self-hosted apps, and small-team CI runners. Scale up to Professional or Performance plans as your container count grows.
    >
    - 4 vCPU, 8 GB RAM, 100 GB NVMe on the Starter tier
    - Unmetered bandwidth, no per-container fees
    - Ubuntu 24.04 LTS available at deploy time
    - SSH key provisioning and IPv6 on every plan
    >
    Deploy your Docker VPS now.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket