How to Install Docker & Portainer on Ubuntu 26.04

install docker portainer on ubuntu 26.04 with Docker Engine, a private HTTPS dashboard, persistent storage, and copy-paste verification steps for a fresh VPS.
A polished 3D server rack with glowing containers and a dashboard panel on a midnight-blue background.

This guide shows you how to install Docker Portainer on Ubuntu 26.04 and reach Portainer CE through a private HTTPS dashboard in about ten minutes. You will finish with Docker Engine, Compose v2, persistent Portainer data, and no publicly exposed management port.

Prerequisites

  • A 64-bit Ubuntu 26.04 (Resolute) VPS with at least 2 GB of RAM and 10 GB of free disk space for images, containers, and Portainer data.
  • SSH access as root, or a normal Linux account with working sudo privileges.
  • Outbound HTTPS access to Ubuntu’s repositories, Docker’s package repository, and Docker Hub.
  • An SSH client on your workstation for the local tunnel. A domain is optional because this private setup uses 127.0.0.1; a public reverse proxy needs a domain and trusted TLS certificate later.
  • A VPSLake Linux VPS if you need a root-access Ubuntu server for Docker workloads.

The commands below use sudo. If you are logged in as root, remove sudo from each command.

Step 1: Confirm Ubuntu 26.04 and update the host

Checking the release and architecture first prevents you from adding a repository for the wrong Ubuntu version.

grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME|UBUNTU_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update
sudo apt upgrade -y

The release should identify Resolute and the architecture should be amd64 or another Docker-supported 64-bit architecture:

PRETTY_NAME="Ubuntu 26.04 LTS"
VERSION_ID="26.04"
VERSION_CODENAME=resolute
amd64

If the first command reports a different release, stop here. Mixing Docker packages or Ubuntu repositories between releases can leave APT unable to resolve dependencies.

Step 2: Install Docker Engine from the official repository

The Docker repository provides Docker Engine, containerd, Buildx, and the Compose plugin as one supported package set for Ubuntu 26.04.

Remove distribution packages only if they are present; they can conflict with Docker’s packages. This does not remove existing images, containers, or volumes under /var/lib/docker/.

sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc
sudo apt install -y ca-certificates curl
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
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

The final command starts Docker and enables it for future boots. Confirm the daemon, Engine, and Compose plugin before deploying Portainer:

sudo systemctl is-active docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version

Expected output will vary as Docker releases new packages, but it should look like this:

active
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y

Run Docker’s small test image to validate registry access and the container runtime:

sudo docker run --rm hello-world

Near the end of the output, Hello from Docker! confirms that the daemon pulled and ran a container successfully. The --rm flag deletes the stopped test container.

Docker’s official Ubuntu installation guide documents this repository format and the supported 26.04 release.

Step 3: Allow one trusted account to run Docker

Docker’s Unix socket is root-owned by default, so adding your administration account to the docker group avoids repeated sudo prompts after you reconnect.

Run these commands only as the regular sudo-enabled SSH user who will manage containers. Skip this step if you deliberately administer the server as root:

sudo groupadd --force docker
sudo usermod -aG docker "$USER"

Log out of SSH completely and reconnect, then verify the new group membership:

id -nG
docker info --format 'Server {{.ServerVersion}}'

The group list should contain docker, and docker info should print the Engine version without sudo. Membership grants root-equivalent control over the host, so include only trusted administrator accounts.

Step 4: Deploy Portainer CE with persistent storage

The named volume keeps Portainer users, settings, and environment data when you recreate or upgrade the container.

The following deployment uses Portainer’s LTS image, binds HTTPS to the VPS loopback address, and omits port 8000 because this single-host setup does not use Edge Agents. If you skipped Step 3, prefix these Docker commands with sudo.

docker volume create portainer_data
docker run -d \
  --name portainer \
  --restart=always \
  --publish 127.0.0.1:9443:9443 \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume portainer_data:/data \
  portainer/portainer-ce:lts

Docker should print a long container ID. Check that Portainer is running and that its port is bound only to loopback:

docker ps --filter name=portainer --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES      IMAGE                       STATUS          PORTS
portainer  portainer/portainer-ce:lts Up ...           127.0.0.1:9443->9443/tcp

Portainer generates a self-signed certificate for its first HTTPS connection. Its official Docker installation guide covers the LTS image, the Docker socket, and the persistent data volume.

Step 5: Open Portainer through an SSH tunnel

The loopback binding keeps port 9443 off the public interface; the SSH tunnel lets your workstation reach it without adding a firewall exception.

Run this command on your local computer, not inside the VPS shell. Replace the uppercase placeholders, then leave this terminal open:

ssh -N -L 9443:127.0.0.1:9443 YOUR_LINUX_USER@SERVER_IP

Open https://127.0.0.1:9443 in your browser. Accept the browser warning for the self-signed certificate, create a strong unique administrator password, and finish the initial Portainer setup. If port 9443 is already in use on your workstation, use 19443:127.0.0.1:9443 in the SSH command and browse to https://127.0.0.1:19443 instead.

Verify Docker and Portainer

These checks prove that Docker starts at boot, Portainer is healthy, its restart policy is set, and the private HTTPS listener answers locally on the VPS:

systemctl is-active docker
systemctl is-enabled docker
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' portainer
curl --insecure --head --max-time 10 https://127.0.0.1:9443
active
enabled
always
HTTP/1.1 200 OK

Reconnect the SSH tunnel if necessary and reload https://127.0.0.1:9443. The Portainer dashboard should show the local Docker environment and the administrator account you just created.

Troubleshooting

Package 'docker-ce' has no installation candidate

APT is usually reading the wrong suite or the Docker source file was not created correctly. Check that Ubuntu reports resolute, then inspect the source and refresh the package list:

grep -E '^(Suites|URIs|Components|Architectures|Signed-By):' /etc/apt/sources.list.d/docker.sources
sudo apt update
apt-cache policy docker-ce

The source should use https://download.docker.com/linux/ubuntu, resolute, and stable. Do not substitute noble or install packages from a different Ubuntu release.

permission denied while trying to connect to the Docker daemon socket

Your current SSH session was opened before the group change, or you added a different account. Log out completely, reconnect as the account you modified, and confirm docker appears in id -nG. Use sudo docker info as a temporary diagnostic, not as a replacement for checking the group membership.

The browser cannot connect to 127.0.0.1:9443

The dashboard is intentionally private, so visiting https://SERVER_IP:9443 directly will not work. Start the tunnel from your workstation and keep it running:

ssh -N -L 9443:127.0.0.1:9443 YOUR_LINUX_USER@SERVER_IP

On the VPS, docker ps should show 127.0.0.1:9443->9443/tcp. If it does not, inspect the startup error with docker logs portainer.

Portainer starts with a fresh setup screen after recreation

The container is using a different or missing data volume. Confirm that portainer_data exists and is mounted at /data:

docker volume inspect portainer_data
docker inspect --format '{{range .Mounts}}{{println .Name .Destination}}{{end}}' portainer

Do not delete the volume during troubleshooting. It contains Portainer’s database and removing it intentionally resets the installation.

Hardening

  • Keep Portainer bound to 127.0.0.1 and administer it through SSH. If a team needs public access, place it behind a reverse proxy with a trusted certificate, authentication controls, and a restricted source-IP firewall rule.
  • Do not publish Docker’s unauthenticated TCP API on port 2375. Docker warns that published container ports can bypass ordinary UFW expectations, so review the packet-filtering guidance before exposing application ports.
  • Treat both the Docker socket mount and docker group membership as root-equivalent access. Remove old administrator accounts and protect their SSH keys.
  • Back up the portainer_data volume before changing the Portainer image or host storage layout.

FAQ

Do I need to open port 9443 in UFW?

No. This guide binds Portainer to the VPS loopback interface and reaches it through SSH, so there is no public 9443 listener to allow. If you later bind the service to a public address, restrict the source IP and use a trusted TLS certificate before exposing it.

Why is port 8000 not included in the Docker command?

Port 8000 is Portainer’s optional Edge Agent tunnel. A Portainer server managing its local Docker Engine through /var/run/docker.sock does not need that port, so leaving it unpublished reduces the attack surface.

Can I use a domain name instead of an SSH tunnel?

Yes, but the safer public design is a reverse proxy in front of Portainer with HTTPS, access controls, and a narrow firewall rule. Point the proxy at the loopback listener and do not expose Docker’s socket or an unauthenticated management endpoint.

Where does Portainer store its configuration?

The portainer_data Docker volume stores Portainer’s database, users, settings, and environment configuration. Keeping that volume mounted at /data allows you to replace the container without losing the dashboard state.

Samrat Ghosh
Written by

Samrat Ghosh

Founder & Infrastructure Engineer

Samrat Ghosh is the founder of VPSLake, where he builds and runs the remote desktop and VPS hosting infrastructure the company is built on. He writes hands-on guides about RDP, Windows Server, VPS management and secure remote access - the practical documentation he wishes had…

Previous Article

How to Install Docker and Portainer on Ubuntu 25.10

Next Article

Best VPS for Nextcloud in 2026: Storage, RAM and Cost

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨