Containers

Install Portainer on Ubuntu 26.04 LTS

Portainer is the browser interface Docker admins install the day after they install Docker. It shows every container, image, volume, and network at a glance, deploys Compose stacks from a textarea, and lets you exec into a container without ever typing a docker command. This guide installs Portainer CE on Ubuntu 26.04 LTS with Docker 29.1, Nginx reverse proxy, and Let’s Encrypt TLS.

Original content from computingforgeeks.com - post 167044

The ending is a printable UI-to-CLI cheatsheet that maps every common Portainer action to its raw docker equivalent. Pin it next to your monitor and you stop context-switching when you read the UI but someone else wrote a script.

Tested April 2026 on Ubuntu 26.04 LTS (kernel 7.0.0-10) with Docker 29.4.1, Compose v5.1.3, and Portainer Community Edition 2.39.1 LTS.

Prerequisites

  • Ubuntu 26.04 LTS server, 1 vCPU and 1 GB RAM is enough for Portainer itself; the apps you manage need their own resources.
  • Domain with A record pointing at the server, port 80 reachable for Let’s Encrypt.
  • Sudo user. Finish the post-install baseline checklist and enable SSH key authentication before anything else.

Step 1: Set reusable shell variables

Export the values once and the rest of the guide uses them:

export APP_DOMAIN="portainer.example.com"
export PORTAINER_ROOT="/opt/portainer"
export DATA_VOLUME="portainer_data"
export ADMIN_EMAIL="[email protected]"

Confirm before running the rest:

echo "Domain: ${APP_DOMAIN}"
echo "Volume: ${DATA_VOLUME}"

The output above confirms the step worked. The next section builds on it.

Step 2: Install Docker Engine

Portainer runs as a Docker container that talks to the host Docker daemon through the Unix socket. Install Docker CE from the official repository:

sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg
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
echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable' \
    | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
    docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker --version

The Docker install walkthrough lives in the dedicated Docker install guide. The noble suite is what Docker currently ships for Ubuntu; packages work cleanly on 26.04’s kernel 7.0.

Step 3: Deploy Portainer CE

Portainer CE ships one container and one named volume. Bind the volume for persistent state; mount the Docker socket read-only is tempting but Portainer needs write access to start containers and pull images, so keep it read-write.

sudo docker volume create ${DATA_VOLUME}
sudo docker run -d \
    -p 127.0.0.1:9443:9443 -p 127.0.0.1:9000:9000 \
    --name portainer --restart=always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v ${DATA_VOLUME}:/data \
    portainer/portainer-ce:lts

The port bindings are scoped to 127.0.0.1 on purpose. Portainer only listens on loopback; Nginx is the public entry. This keeps raw port 9000/9443 off the firewall.

Confirm the container started:

sudo docker ps --filter name=portainer --format 'table {{.Names}}\t{{.Status}}'
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9000/

The HTTP endpoint returns 200 on the landing page. Nginx is next.

Step 4: Install Nginx with Let’s Encrypt

Run the commands below to complete this step.

sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
    nginx certbot python3-certbot-nginx ufw

Write the vhost with a placeholder; sed substitutes the real domain in the next command:

sudo tee /etc/nginx/sites-available/portainer.conf > /dev/null <<'EOF'
server {
    listen 80;
    server_name PTR_DOMAIN_HERE;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name PTR_DOMAIN_HERE;

    ssl_certificate     /etc/letsencrypt/live/PTR_DOMAIN_HERE/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/PTR_DOMAIN_HERE/privkey.pem;

    client_max_body_size 100M;
    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;

    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 600;
    }
}
EOF

sudo sed -i "s/PTR_DOMAIN_HERE/${APP_DOMAIN}/g" /etc/nginx/sites-available/portainer.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/portainer.conf /etc/nginx/sites-enabled/portainer.conf
sudo nginx -t

Open the firewall and issue the certificate:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable

sudo certbot --nginx -d "${APP_DOMAIN}" \
    --non-interactive --agree-tos --redirect \
    -m "${ADMIN_EMAIL}"

The Nginx and Let’s Encrypt walkthrough covers DNS-01 for private networks.

Step 5: Create the admin account

Browse to https://${APP_DOMAIN}/. Portainer’s first-run screen prompts for an admin username and password:

Portainer Admin Initialization on Ubuntu 26.04

Set a username, a password that meets the Portainer minimum (12 characters, 1 number, 1 uppercase, 1 symbol), and finish. Portainer signs you in and opens the environment picker.

Step 6: Connect the local Docker endpoint

The first-run wizard adds the local Docker socket as an environment automatically. You land on the Environments page showing one card:

Portainer Local Docker Environment on Ubuntu 26.04

Click through to the dashboard. The Environment info panel shows Docker 29.4.1 in standalone mode, 1 container (Portainer itself), 1 image, 1 volume, and 3 networks (bridge/host/none):

Portainer Local Docker Dashboard on Ubuntu 26.04

With that step done, move on to the next one.

Step 7: Tour the UI

The left sidebar is where you will spend most of your time. Five pages do 90% of the daily work:

  • Containers: every running and stopped container with a Quick actions column for logs, stats, exec, and inspect.
  • Images: pull, tag, push, and prune Docker images without touching a CLI.
  • Volumes: inspect, browse, and delete named volumes and bind mounts.
  • Networks: create user-defined networks, attach containers, and trace routing.
  • Stacks: deploy docker-compose.yml files with a paste-in editor that handles templating and webhook redeploys.

The Containers page lists everything in the local Docker daemon, including Portainer itself:

Portainer Containers List on Ubuntu 26.04

Terminal check of the stack:

Portainer Docker status on Ubuntu 26.04

Those commands set the baseline. Continue with the next step.

Step 8: Deploy your first stack

Stacks are Portainer’s name for Docker Compose files. Copy the compose YAML below into Stacks → Add stack → Web editor to deploy a simple whoami service behind a Traefik sidecar:

services:
  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"

Portainer’s stacks page shows the new stack as soon as deployment completes:

Portainer Stacks List on Ubuntu 26.04

Test the new service:

curl -s http://localhost:8080/ | head -10

From here you can add Traefik, attach the whoami service to it, and expose multiple stacks behind a single reverse proxy. That is a normal homelab next step.

Step 9: Add a second Docker host via the Portainer Agent

The power of Portainer scales when you manage multiple Docker hosts from one UI. On the second host, install Docker (same as Step 2), then run the Portainer Agent:

sudo docker run -d \
    -p 127.0.0.1:9001:9001 \
    --name portainer_agent --restart=always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /var/lib/docker/volumes:/var/lib/docker/volumes \
    -v /:/host \
    portainer/agent:lts

On the Portainer UI host, open a reverse SSH tunnel or expose port 9001 over WireGuard, then in the main Portainer: Environments → Add environment → Docker Standalone → Agent, set the URL to the agent’s address and port 9001. Portainer auto-discovers the remote stack and you manage both hosts from the same sidebar.

Step 10: Set up access control

Portainer CE supports teams and role-based access without a license. Users → Add user creates standard users that can only see environments you assign them. For read-only dashboards (for example, giving a junior engineer visibility into production without the ability to restart containers), create a team, assign the environment with “Standard user” access, and add the user to the team.

For Business Edition features (OAuth login, RBAC, activity audit), Portainer CE includes a small upgrade banner; the community edition covers everything this guide needs.

Troubleshooting

“Unable to connect to endpoint: Get unix:///var/run/docker.sock: dial unix: permission denied”

The Portainer container cannot read the Docker socket. Two causes:

  1. The socket path in the docker run command is wrong. Ubuntu 26.04 uses /var/run/docker.sock; verify with sudo ls -la /var/run/docker.sock.
  2. AppArmor blocked the access. Check sudo dmesg | grep -i apparmor | tail. If blocked, add the Portainer container to the allowlist or re-run with --security-opt apparmor=unconfined (only for local debugging).

“Login failed” immediately after the admin was created

Portainer rate-limits failed logins. Wait 60 seconds and retry. If the rate-limit does not clear, the admin account password hash is corrupted:

sudo docker stop portainer
sudo docker run --rm -v portainer_data:/data \
    portainer/helper-reset-password

The helper container resets the admin password to a random string printed in the container logs. Start Portainer again with sudo docker start portainer.

WebSocket closes during container terminal exec

Nginx proxy_read_timeout defaults to 60 seconds. The vhost in Step 4 sets 600 seconds, which covers most cases. For long-running docker exec sessions, raise to 3600:

sudo sed -i 's/proxy_read_timeout 600/proxy_read_timeout 3600/' \
    /etc/nginx/sites-enabled/portainer.conf
sudo nginx -t && sudo systemctl reload nginx

Configuration is now in place. Proceed to the next section.

“docker: Error response from daemon: Conflict. The container name ‘/portainer’ is already in use”

A previous run left the container record in Docker. Remove and redeploy:

sudo docker rm -f portainer
# Re-run the docker run command from Step 3

The named volume portainer_data is not deleted; your admin account and environment config survive the container replacement.

Portainer UI to Docker CLI cheatsheet

This cheatsheet maps every common Portainer action to its raw docker or docker compose equivalent. Print it, bookmark it, share it with teammates who need to translate between the two.

Portainer UI actionDocker CLI equivalent
Home → Environments → clickdocker context use <name>
Containers → click namedocker inspect <name>
Containers → Quick actions → Logsdocker logs -f <name>
Containers → Quick actions → Statsdocker stats <name>
Containers → Quick actions → Execdocker exec -it <name> sh
Containers → Restartdocker restart <name>
Containers → Stopdocker stop <name>
Containers → Removedocker rm -f <name>
Containers → Recreatedocker rm -f <name> && docker run ...
Containers → Duplicate/Edit → Deploydocker run ... with edited flags
Images → Pull imagedocker pull <image:tag>
Images → Build a new imagedocker build -t <tag> .
Images → Remove unuseddocker image prune -a -f
Volumes → Add volumedocker volume create <name>
Volumes → Removedocker volume rm <name>
Volumes → Browsedocker run --rm -v <name>:/data alpine ls /data
Networks → Add networkdocker network create <name>
Networks → Connect containerdocker network connect <net> <container>
Stacks → Add stack → Web editor → Deploydocker compose -f stack.yml up -d
Stacks → Stopdocker compose -f stack.yml down
Stacks → Redeploy with pulled imagedocker compose pull && docker compose up -d
Stacks → Editor → Edit YAML → UpdateEdit stack.yml and docker compose up -d
Host → System → Prune alldocker system prune -a --volumes -f
Registries → Add registrydocker login <registry>
Eventsdocker events --since 1h

The UI and the CLI are not competing; they are different lenses on the same Docker daemon. Portainer wins for discovery (what containers exist, what images are wasting disk) and for non-Docker-native teammates. The CLI wins for scripting, CI/CD, and for the ten common tasks you do a hundred times a day. Pair them with the server hardening guide so the underlying Ubuntu host stays tight as the container set grows.

Related Articles

Databases Install MariaDB on Ubuntu 26.04 LTS Debian How To Install Memcached on Debian 12/11 AlmaLinux Setup Docker Swarm Cluster on Rocky Linux 10 / AlmaLinux 10 Containers Kubernetes Node Management in Rancher

Leave a Comment

Press ESC to close