Install Docker Engine and Docker Compose on Ubuntu VPS

What Is Docker and Why Use It on VPS?

Docker is a containerization platform that packages applications together with their dependencies (libraries, environment variables, configuration files) into isolated units called containers. Unlike virtual machines, containers share the host OS kernel, making them lightweight and fast to start — typically within seconds.

On a VPS, Docker provides several key advantages:

  • Environment consistency — "Works on my machine" problems disappear; the container behaves identically across dev, staging, and production
  • Easy deployment — Start complex software (databases, message queues, search engines) with a single command
  • Isolation — Applications run in separate containers, preventing dependency conflicts
  • Rollback capability — Tag images by version and roll back instantly if an update breaks something

This guide assumes a fresh Ubuntu 22.04 VPS with root or sudo access.

Why install from Docker's official repo? Ubuntu's built-in apt packages Docker as docker.io, which is often several versions behind. Installing from Docker's official repository ensures you get the latest stable Docker Engine and the docker-compose-plugin (v2 syntax: docker compose instead of docker-compose).

Step 1: Prepare the System

Update package lists and install prerequisite packages needed to add Docker's apt repository over HTTPS:

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release

Add Docker's Official GPG Key

Docker signs its packages with a GPG key. Add it to Ubuntu's trusted keyring so apt can verify package integrity:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

Add Docker's Repository

Add the official Docker apt repository to your sources list:

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Step 2: Install Docker Engine and Compose Plugin

Update the package index (now including Docker's repo) and install all required packages:

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

This installs Docker Engine (docker-ce), the CLI client (docker-ce-cli), the container runtime (containerd.io), and the modern Compose plugin.

Verify the Installation

Run the classic hello-world container to confirm Docker is working correctly:

sudo docker run hello-world

You should see a message: "Hello from Docker! This message shows that your installation appears to be working correctly."

Step 3: Run Docker Without sudo

By default, only root can run Docker commands. Add your user to the docker group so you can run containers without sudo:

sudo usermod -aG docker $USER

Log out and log back in for the group change to take effect. Then verify without sudo:

docker run hello-world

Security note: The docker group grants root-equivalent access on the host because Docker can mount host filesystems. Only add trusted users to this group. Never add the www-data or other service accounts.

Step 4: Run Your First Real Container — Nginx

Let's run a real web server to see Docker in action. The following command runs an Nginx container that maps port 8080 on the host to port 80 inside the container:

docker run -d -p 8080:80 --name my-nginx nginx:alpine

Flags explained:

  • -d — detached mode (runs in background)
  • -p 8080:80 — maps host port 8080 → container port 80
  • --name my-nginx — assigns a readable name
  • nginx:alpine — uses the lightweight Alpine Linux-based Nginx image

Open http://YOUR_VPS_IP:8080 in your browser. You should see the Nginx welcome page.

Understanding the Docker Workflow

Before going further, it helps to understand the three core concepts you will work with daily. An image is a read-only template that contains everything an application needs — code, runtime, libraries, and configuration. A container is a running instance created from an image; you can start, stop, and delete containers without affecting the image they came from. A registry (such as Docker Hub) is where images are stored and shared, similar to how GitHub stores code.

The typical lifecycle looks like this: you pull an image from a registry, run it to create a container, inspect its logs, and eventually stop and rm it. When you need a custom application you write a Dockerfile, then build it into your own image. On a VPS this workflow means you can deploy a new version of your app by pulling a fresh image and recreating the container — a process that takes seconds and is trivially easy to roll back.

Term What it is
ImageRead-only template — the blueprint of your application
ContainerA running, writable instance of an image
VolumePersistent storage that survives container deletion
NetworkPrivate virtual network letting containers talk to each other
RegistryStorage for images (Docker Hub, GitHub Container Registry)

Essential Docker Commands

These commands cover 90% of daily Docker usage:

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Stop a container
docker stop my-nginx

# Remove a container
docker rm my-nginx

# List downloaded images
docker images

# Pull an image without running it
docker pull redis:7-alpine

# View container logs (follow mode)
docker logs -f my-nginx

# Execute a command inside a running container
docker exec -it my-nginx sh

Step 5: Deploy a Multi-Container App with Docker Compose

Real applications rarely run as a single container. Docker Compose lets you define and run multi-container apps using a single YAML file. Here's a classic example — WordPress with a MySQL database:

mkdir ~/wordpress && cd ~/wordpress

Create docker-compose.yml:

version: "3.9"
services:
  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppassword
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:latest
    restart: always
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppassword
    volumes:
      - wp_data:/var/www/html
    depends_on:
      - db

volumes:
  db_data:
  wp_data:

Start the entire stack with one command:

docker compose up -d

Docker Compose automatically creates a private network between the two containers. WordPress can reach MySQL using the hostname db — the service name in the YAML file. To stop and remove everything:

docker compose down

To also delete the data volumes (WARNING: data will be lost):

docker compose down -v

Step 6: Enable Docker to Start on Boot

Docker's systemd service should already be enabled after installation. Verify with:

sudo systemctl is-enabled docker
# Should output: enabled

sudo systemctl status docker

If not enabled, run:

sudo systemctl enable docker --now

Containers started with --restart always or restart: always in Compose files will automatically restart when the Docker daemon starts.

Step 7: Clean Up Docker Resources

Over time, stopped containers, unused images, and dangling volumes accumulate and consume disk space. Use these commands to reclaim disk space:

# Remove all stopped containers
docker container prune

# Remove unused images
docker image prune

# Remove all unused resources at once (containers, images, networks, build cache)
docker system prune

# Show disk usage by Docker
docker system df

Summary and Next Steps

You have successfully installed Docker Engine and Docker Compose on Ubuntu 22.04, run your first container, and deployed a multi-container WordPress+MySQL stack using Docker Compose.

Next steps to explore:

  • Portainer — a web UI to manage Docker containers visually without CLI
  • Nginx reverse proxy — route traffic from port 80/443 to multiple containers
  • Docker volumes — persistent storage for databases and user uploads
  • Private Docker registry — store your own images without relying on Docker Hub

Basic Docker Security on a VPS

Docker is powerful, but a misconfigured host exposed to the internet can become an easy target. Before running production workloads, apply these baseline hardening steps:

  • Treat the docker group as root — anyone in the docker group can mount the host filesystem into a container and read or modify any file. Add only trusted administrators, and never add service accounts like www-data.
  • Do not expose database ports publicly — when running MySQL, Redis, or PostgreSQL in Compose, let other containers reach them over the internal network instead of publishing the port with ports:. An exposed database with a weak password is one of the most common breach vectors.
  • Keep a firewall in front — configure UFW to allow only the ports you actually need (80, 443, SSH). Be aware that Docker manipulates iptables directly and can bypass UFW rules; review your iptables rules after publishing ports.
  • Pull updated images regularly — run docker pull image:tag to fetch patched versions, then recreate containers from the new image. Avoid leaving images unpatched for months.
  • Run containers as a non-root user — many images support a --user flag or a USER directive in the Dockerfile, which limits the blast radius if a container is compromised.
  • Limit container resources — use --memory and --cpus flags so a single runaway container cannot exhaust the entire VPS and take down your other services.

Frequently Asked Questions

How is Docker different from a virtual machine?

A virtual machine emulates an entire hardware stack and boots a full guest operating system, which makes it resource-heavy and slow to start. A Docker container shares the host kernel and isolates only the process, filesystem, and network, so it is lightweight, starts in seconds, and lets you run many containers on a single VPS. The trade-off is that all containers must run on the same kernel type (Linux).

How much RAM does my VPS need to run Docker?

For testing and lightweight containers such as Nginx or a static site, 1GB of RAM is enough. To run WordPress with MySQL or several services at once, 2GB or more is recommended. AsiaGB VPS plans start at just 500 THB/month with Ubuntu and several RAM tiers so you can match the plan to your actual workload.

Why install from Docker's repository instead of the default apt package?

Ubuntu's bundled docker.io package is usually several versions behind and lacks the modern docker compose v2 plugin. Installing from Docker's official repository gives you the latest, security-patched Docker Engine and Compose plugin with full support for current features.

How do I make containers start automatically after a reboot?

Add --restart always when running docker run, or set restart: always in your docker-compose.yml. Docker will then start those containers automatically whenever the daemon starts after a reboot. Make sure the Docker service itself is enabled at boot with sudo systemctl enable docker.

Ready to run Docker on a VPS?

AsiaGB VPS starts at just 500 THB/month with full root access, Ubuntu 22.04, and 99% uptime SLA — everything you need to run Docker containers in production.

View VPS Plans →