Verified guide
This guide was verified by creating a cloud server and running every command in it, from start to finish, exactly as written.
- OS
- Ubuntu 22.04.5 LTS · x86_64
- Installed version
- Docker 29.9.0 (docker-ce), Docker Compose 5.6.0, buildx 0.38.0, containerd 2.4.1
- Test server
- DigitalOcean · 1 vCPU / 1 GB RAM / 25 GB SSD (s-1vcpu-1gb, sgp1)
- Last checked
- 2026-10-13
This guide contains affiliate links. If you sign up through them, Codesand may earn a commission. This doesn't affect the content or our assessments.
Add Docker's official repository to an Ubuntu 22.04 server and install Docker Engine and Docker Compose, then set up docker without sudo, run a container with Compose, deal with ports published by Docker bypassing the ufw firewall, and remove everything.
1. Requirements
- One Ubuntu 22.04 LTS server (1 GB of memory or more recommended)
- SSH access with a regular user account that has
sudorights - A PC for checking access from outside (used in the firewall step)
If you do not have a server, create the smallest DigitalOcean Droplet (1 vCPU · 1 GB). It is enough to install Docker and run containers. Every step in this guide was tested on a Droplet (1 vCPU · 1 GB memory · 25 GB SSD · Ubuntu 22.04 · Singapore) with a regular user account that has sudo rights.
2. Choose the package to install
The default Ubuntu 22.04 repository also has a Docker package (docker.io).
apt-cache policy docker.io | head -3docker.io:
Installed: (none)
Candidate: 29.1.3-0ubuntu3~22.04.2This guide installs docker-ce from the official repository that Docker publishes itself. This is the method in Docker's official documentation, and you get new releases as soon as they are out. Docker Compose is installed together as the docker compose command.
Packages from the default repository or other container tools can conflict if they are already installed, so remove them. Packages that are not installed show a line such as Package 'docker.io' is not installed, so not removed and are skipped, so you can run this on a fresh server too.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt remove -y $pkg; done3. Add Docker's official repository
Install the tools needed to add the repository. They are usually already installed.
sudo apt update
sudo apt install -y ca-certificates curlDownload and save the signing key of the Docker repository. The signing key is used to check that the packages you download were made by Docker. -m 0755 sets the folder permissions, and a+r lets every user read the key file.
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.ascRegister the repository address. The command in the Suites line is replaced with the server's Ubuntu codename. For Ubuntu 22.04, it is jammy.
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOFTypes: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: jammy
Components: stable
Signed-By: /etc/apt/keyrings/docker.ascRefresh the package list and check the version that will be installed. If Candidate is the version from the Docker repository, everything is fine.
sudo apt updateapt-cache policy docker-ce | head -3docker-ce:
Installed: (none)
Candidate: 5:29.9.0-1~ubuntu.22.04~jammy4. Install Docker
Install Docker Engine (docker-ce), the command-line tool (docker-ce-cli), the container runtime (containerd.io), the image build tool (docker-buildx-plugin), and Docker Compose (docker-compose-plugin) together.
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCheck the installed versions.
docker --versionDocker version 29.9.0, build f415da8docker compose versionDocker Compose version v5.6.0docker buildx versiongithub.com/docker/buildx v0.38.0 0dbe87f36ed472fe36b6796c3f70b4210a731fc55. Check the service status
When installation finishes, the Docker service starts automatically and is registered to start again when the server reboots.
sudo systemctl status docker --no-pager | head -3● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2026-10-13 02:15:35 UTC; 9s agoCheck that both docker and the container runtime containerd are set to start automatically.
systemctl is-enabled docker containerdenabled
enabledDownload and run the test image hello-world. If the message below appears, Docker works.
sudo docker run hello-worldHello from Docker!
This message shows that your installation appears to be working correctly.6. Use docker without sudo
Right now, running it without sudo gives the error below. The docker command uses the Docker service's socket file (/var/run/docker.sock), and only root and the docker group can use that file.
docker run hello-worldpermission denied while trying to connect to the docker API at unix:///var/run/docker.sockAdd the current user to the docker group. -aG adds the group while keeping the existing groups, and $USER is the name of the user who is logged in now.
sudo usermod -aG docker $USERThe group change takes effect after you log in again. Close the SSH connection, connect again, and check. You are done when docker appears in the group list.
groupsdevuser sudo dockerdocker run hello-world | head -3
Hello from Docker!
This message shows that your installation appears to be working correctly.This is the list of containers run so far. -a also shows containers that have finished running.
docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0590eaaddfa5 hello-world "/hello" 1 second ago Exited (0) Less than a second ago sweet_banach
19e77a86a647 hello-world "/hello" 5 seconds ago Exited (0) 4 seconds ago gifted_wu7. Run a container with Docker Compose
Run Docker Compose as docker compose (with a space). The old docker-compose (with a hyphen) command is not installed. If another guide says docker-compose up, run docker compose up instead.
Create a compose.yaml file that runs the nginx web server. "8080:80" connects port 8080 on the server to port 80 in the container.
mkdir ~/hello-compose && cd ~/hello-compose
cat > compose.yaml <<EOF
services:
web:
image: nginx:1.29
ports:
- "8080:80"
EOFStart the container. -d runs it in the background without holding the terminal. The first time, several lines appear while the nginx image downloads, and the lines below appear at the end.
docker compose up -d Container hello-compose-web-1 Creating
Container hello-compose-web-1 Created
Container hello-compose-web-1 Starting
Container hello-compose-web-1 StartedCheck the container status and connect from inside the server. If Welcome to nginx! appears, everything works.
docker compose psNAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
hello-compose-web-1 nginx:1.29 "/docker-entrypoint.…" web 3 seconds ago Up 1 second 0.0.0.0:8080->80/tcp, [::]:8080->80/tcpcurl -s http://localhost:8080 | grep -o "<title>.*</title>"<title>Welcome to nginx!</title>8. The firewall (ufw) and Docker ports
Turn on the ufw firewall and allow only SSH. Always allow SSH before turning on the firewall. If you turn on the firewall without allowing SSH, your connection to the server is cut off. --force turns on the firewall without asking for confirmation.
sudo ufw allow OpenSSHRules updated
Rules updated (v6)sudo ufw --force enableFirewall is active and enabled on system startupsudo ufw statusStatus: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)The firewall has no rule for port 8080. Yet when you connect to port 8080 of the server from another PC, the nginx page opens. Replace 203.0.113.20 with your server's IP.
curl -sS --max-time 10 http://203.0.113.20:8080 | grep -o "<title>.*</title>"<title>Welcome to nginx!</title>This is because Docker adds its own rules to the Linux firewall (iptables) when it publishes a port, and those rules are applied before the ufw rules. You can see the rule Docker added for port 8080 with the command below.
sudo iptables -t nat -L DOCKER -n | grep 8080DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.18.0.2:80For a service that only needs to be reached from inside the server, put 127.0.0.1: in front of the port so it is bound only to the server's own address. sed -i changes the file content and saves it right away. After changing the file, running docker compose up -d again recreates the container with the new settings.
sed -i 's/"8080:80"/"127.0.0.1:8080:80"/' compose.yaml
cat compose.yamlservices:
web:
image: nginx:1.29
ports:
- "127.0.0.1:8080:80"docker compose up -d Container hello-compose-web-1 Recreate
Container hello-compose-web-1 Recreated
Container hello-compose-web-1 Starting
Container hello-compose-web-1 StartedPort 8080 is now open only on 127.0.0.1, and it still works from inside the server.
sudo ss -tlnp | grep 8080LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=5816,fd=8))curl -s http://localhost:8080 | grep -o "<title>.*</title>"<title>Welcome to nginx!</title>If you connect from another PC again, the connection now fails.
curl -sS --max-time 10 http://203.0.113.20:8080curl: (28) Connection timed out after 10005 millisecondsFor a web service that must be public, we recommend putting a web server such as Nginx in front and binding container ports only to 127.0.0.1.
9. Troubleshooting
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
The current user is not in the docker group. Run sudo usermod -aG docker $USER as in step 6, then close the SSH connection and connect again. The group change does not take effect until you connect again. Check that docker appears in the output of groups.
docker-compose: command not found
With the method in this guide, Compose runs as the docker compose (with a space) command. Run docker compose up -d instead of docker-compose up -d.
A container port is reachable from outside even though ufw blocks it
The rules Docker adds directly to iptables are applied before ufw. As in step 8, change the port in compose.yaml to the form "127.0.0.1:8080:80" so that it can be reached only from inside the server.
10. Uninstall
First, stop and remove the running containers. docker compose down removes the containers and network created from compose.yaml.
cd ~/hello-compose
docker compose down Container hello-compose-web-1 Stopping
Container hello-compose-web-1 Stopped
Container hello-compose-web-1 Removing
Container hello-compose-web-1 Removed
Network hello-compose_default Removing
Network hello-compose_default RemovedRemove the Docker packages and all data. Images, containers, and volumes are all deleted, so back up any data you need first. docker-ce-rootless-extras is a package that was installed together with Docker.
sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
sudo rm -rf /var/lib/docker /var/lib/containerdAlso delete the repository and signing key added in step 3, and refresh the package list.
sudo rm /etc/apt/sources.list.d/docker.sources /etc/apt/keyrings/docker.asc
sudo apt updateCheck that no Docker packages remain. If 0 appears, everything has been removed.
dpkg -l | grep -c -E "docker|containerd"0Try it yourself
No server yet? You can spin up an Ubuntu server in a few minutes with one of these providers. We verified this guide on DigitalOcean.
Was this guide helpful? Let us know if anything is wrong or out of date.
Request a correction