When I first started with Docker, I used to just run apt install docker.io and call it a day. It worked – but not always well.

The Docker and Compose versions from Ubuntu's default repositories were often outdated, and I quickly ran into missing features and annoying compatibility issues.

One example: the newer Docker Compose v2 plugin (which integrates with the docker command) wasn’t available at all – Ubuntu’s repo still had the old, now-deprecated docker-compose v1 binary.

After a bit of digging, I found the better way: installing Docker Engine and the Compose plugin straight from Docker’s official repository. It’s what the Docker team recommends, and it ensures you're always running the latest stable versions.

In this guide, I’ll walk you through the steps I now use – a clean setup that keeps things up-to-date and future-proof.

📬 Newsletter

Subscribe for occasional updates – installation guides, practical tips, and lessons learned.

I’m In!

Uninstall Old Packages

Before installing Docker from the official repository, it’s a good idea to remove any old or conflicting packages that might be lingering from a previous setup.

You can uninstall them all in one go with this loop:

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

This command iterates through common legacy Docker and container packages – such as docker.iodocker-composecontainerd, and more – and uninstalls each one.

🙆‍♂️
Don’t worry: This won’t delete your Docker images or containers. It just clears out the old binaries so you're starting fresh.

Once that’s done, clean up any leftover dependencies:

sudo apt autoremove -y

This step ensures any libraries or packages that were only needed by the old Docker binaries are removed, leaving you with a clean slate for the new installation.

Install Docker from Official Docker Repository

Now it’s time to install Docker Engine and the Docker Compose plugin using Docker’s official APT repository – not the outdated packages from Ubuntu’s default sources.

This ensures you get the latest version of Docker CE (Community Edition) along with the integrated Compose v2 plugin.

Run the following commands to add Docker’s GPG key and repository:

# Update your package list and install tools needed for the setup
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Create a keyring directory (best practice for modern APT key management)
sudo install -m 0755 -d /etc/apt/keyrings

# Download Docker’s official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo tee /etc/apt/keyrings/docker.asc > /dev/null

# Set correct permissions  
sudo chmod a+r /etc/apt/keyrings/docker.asc

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

# Refresh package list again  
sudo apt update

Let’s break down what this does:

  • Installs required packages like curl and gnupg (if not already present) for handling HTTPS and GPG keys.
  • Downloads Docker’s official GPG key and saves it in /etc/apt/keyrings/docker.asc (a secure location for APT keys).
  • Adds Docker’s APT repository to your server’s software sources (pointing to your Ubuntu release codename).
  • Updates the package list to include Docker’s repository.

Once the repo is set up, install Docker and its core components:

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

This installs the Docker daemon (docker-ce) along with:

  • docker-ce-cli: The command-line interface for Docker.
  • containerd.io: The container runtime Docker uses under the hood.
  • docker-buildx-plugin: Docker’s Buildx plugin (for extended build capabilities).
  • docker-compose-plugin: The Docker Compose v2 plugin, which adds the docker compose subcommand.
With this, Docker Engine is installed as a service on your server.

Sometimes Docker doesn’t start automatically after installation. To ensure it’s running, start the service and check its status:

sudo systemctl start docker.service
sudo systemctl status docker.service

If the service is listed as active (running), Docker is ready to use – otherwise, you can inspect its logs with sudo journalctl -u docker.service --no-pager to diagnose any startup issues.

Run Docker Hello-World

To verify that Docker Engine is installed and working properly, run the classic hello-world container:

sudo docker run hello-world

This command downloads a test image and runs it in a container. When the container runs, it prints a Hello from Docker! confirmation message and then exits.

If you see this message, congrats – your Docker installation is successful.

Manage Docker as a non-root user

By default, you need to use sudo for all Docker commands. If you’d prefer to run Docker as your normal user (without sudo):

sudo usermod -aG docker $USER

After running this, log out and SSH back in (or reboot) for the group change to take effect.

Once you’ve SSHed in again, test it by running docker info or docker run hello-world again – this time without the sudo prefix.

Verify Docker Compose Plugin

Now that Docker and the Compose plugin are installed, let’s confirm that Docker Compose v2 is working properly.

Run the following command to see if Docker Compose is recognized:

sudo docker compose version

If everything was set up correctly, this will display the Compose plugin’s version, for example:

Docker Compose version v2.x.x

The important part is that you see a version starting with v2, confirming that you have Docker Compose V2 installed as a plugin.

If you see an error:

  • Double-check that you installed the docker-compose-plugin package.
  • Make sure you're running the command as sudo or that your user is part of the docker group.

Now, rerun the previous command and verify whether Docker Compose is recognized.

Run a Quick Docker Compose Test

Let’s test Docker Compose by launching a simple container.

Create a test directory:

mkdir docker-compose-test && cd docker-compose-test

Create a docker-compose.yml file with the following content:

services:
  hello:
    image: hello-world

This defines a Compose app with one service named hello, using Docker’s tiny hello-world image.

Run the application:

sudo docker compose up

Docker (via the Compose plugin) will pull the hello-world image (if not already pulled) and start the container.

You should see output indicating that Docker is creating and running the container, and then the Hello from Docker! message from the hello-world container.

Compose will manage the container’s lifecycle. Since hello-world exits after printing its message, you’ll likely see Compose stop the container once it’s done.

For example, the output may look like:

[+] Running 2/2
 ✔ Network test_default    Created  0.1s
 ✔ Container test-hello-1  Created  0.1s
Attaching to hello-1
hello-1  |
hello-1  | Hello from Docker!
hello-1  | This message shows that your installation appears to be working correctly.
...
hello-1 exited with code 0

Success! This confirms that docker compose is working, and the Compose plugin is running correctly.

In a real-world app, your containers would typically stay running – like a web service using NGINX or a database – but for testing, hello-world is a simple and safe way to verify the setup.

Conclusion and Final Thoughts

By installing Docker from Docker’s official APT repository, you’ve set yourself up with:

  • The latest Docker Engine
  • The official Docker Compose v2 plugin
  • A setup that follows current best practices for Ubuntu servers

This approach not only gives you access to the newest features (like docker compose instead of the old docker-compose binary), but also ensures smoother upgrades moving forward.

💬 Found this guide helpful?

I’d love to hear your thoughts, questions, or suggestions in the discussion section below. Your feedback helps improve future guides and supports others on their server journey.

Prefer a more direct conversation? Feel free to contact me anytime.