Self-Hosting Portainer: A Complete Guide to Docker Container Management

Self-Hosting Portainer: A Complete Guide to Docker Container Management

Portainer Community Edition is a lightweight, open-source UI for managing Docker hosts, stacks, images, volumes, and remote agents. This guide walks through self-hosting Portainer CE on Ubuntu 24.04 LTS with Docker Compose, HTTPS, first admin setup, remote agents, backups, and upgrades.

· Updated · 12 min read #self-hosted #open-source #deployment #docker #vps #management #ui #containers #portainer

 Self-hosted Portainer CE management UI

Caption: Self-hosted Portainer CE sits in front of your Docker engine so you can manage environments, stacks, and containers from a browser.

Introduction

Portainer is an open-source container management platform that turns day-to-day Docker work into a clear web UI. Instead of memorizing long docker and docker compose flags for every host, you get a central place to inspect containers, deploy stacks, manage images and volumes, and connect additional Docker environments through agents. Portainer Community Edition (CE) is the free, self-hosted edition aimed at homelabs, agencies, and small teams that already run Docker on a VPS or bare metal.

Self-hosting Portainer makes sense when you want visual control without handing your Docker socket to a SaaS console. Solo operators keep inventory across one or two servers; agencies give trusted operators a safer path than raw SSH for routine restarts and log checks. Because the server mounts the Docker socket (or talks to remote agents), treat it as privileged infrastructure: HTTPS, a strong admin password, and careful roles are required.

This guide installs Portainer CE on Ubuntu 24.04 LTS with the official Compose pattern and portainer/portainer-ce:lts. You will create a data volume, publish the UI on 9443, optionally keep 8000 for Edge features, put TLS in front, finish admin setup, connect the local environment, add a remote agent, then cover troubleshooting, backups, and upgrades. Replace example hostnames before production use.

Why Choose Portainer?

  • Web UI for Docker: Browse containers, images, volumes, networks, and Compose stacks without living in the terminal for every routine task.
  • Official LTS image: The documented portainer/portainer-ce:lts tag tracks the Community Edition long-term support channel recommended for servers.
  • Local and remote hosts: Manage the host where Portainer runs via the Docker socket, then add more Linux Docker nodes with the Portainer Agent.
  • Stacks from Compose: Deploy and update multi-service apps from Compose YAML through the UI while keeping files under version control when you prefer.
  • Lightweight footprint: The server is a single container plus a named volume; it fits comfortably on a modest VPS that already runs Docker.
  • Role-aware access: Invite teammates with limited scopes instead of sharing root SSH for every restart and log tail.
  • Edge-ready tunnel port: Port 8000 is available when you later adopt Edge Agents; omit it if you do not need that path.

Treat Portainer as a privileged control plane. An admin can start containers with host mounts, read volume secrets, and alter networks. Plan firewall rules, TLS, password policy, and admin seats before exposing 9443 publicly.

Prerequisites

Hardware Recommendations:

  • Comfortable single-node lab: 1–2 vCPU and 1–2 GB RAM for Portainer itself on a host that already runs a few stacks
  • Busier multi-environment setups: 2–4 vCPU and 4 GB RAM on the Portainer server host
  • 10 GB+ free SSD for the OS, Docker images, and the portainer_data volume
  • Off-server backup capacity for the Portainer volume and your Compose files
  • Optional second Docker host when you want to practice remote Agent enrollment

Software and Accounts:

  • Ubuntu 24.04 LTS with sudo access
  • A public hostname such as portainer.example.com (or a private hostname if you only access via VPN)
  • DNS A/AAAA records pointing at the server when you terminate TLS on a reverse proxy
  • Docker Engine with the Compose v2 plugin (install from Docker’s apt repository; avoid snap Docker on Ubuntu)
  • OpenSSL, curl, and a password manager for the initial admin account
  • Optional reverse proxy (Caddy, Traefik, or nginx) for a trusted certificate on 443

Security Notes:

  • Prefer the :lts tag over floating :latest on production hosts
  • Do not expose raw Docker TCP without mutual TLS; Portainer’s documented path uses the Unix socket or Agent HTTPS
  • Keep Compose files and backup archives private — they imply how your control plane is wired
  • Open only SSH (admin), HTTP, and HTTPS on the public firewall when a reverse proxy fronts Portainer; otherwise restrict 9443 to trusted IPs or a VPN
  • Schedule admin password rotation and limit the number of admin users

Patch the host and set a tight firewall:

sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg openssl ufw

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Only if you will hit Portainer TLS directly without a reverse proxy:
# sudo ufw allow 9443/tcp
sudo ufw enable
sudo ufw status

Installation Guide

This deployment follows the official Portainer CE Docker Compose approach on Linux: install Docker Engine, write portainer-compose.yaml, start the server, complete initial setup at https://…:9443, then optionally place a reverse proxy in front and enroll remote agents.

1. Install Docker Engine

sudo apt-get update -qqy
sudo apt-get install ca-certificates curl -qqy
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=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update -qqy
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -qqy

sudo usermod -aG docker "$USER"
newgrp docker

docker --version
docker compose version

Confirm the daemon is healthy:

docker run --rm hello-world

2. Create the Portainer Directory and Compose File

sudo mkdir -p /opt/portainer
sudo chown "$USER":"$USER" /opt/portainer
cd /opt/portainer

Create portainer-compose.yaml using the official Community Edition LTS pattern:

services:
  portainer:
    container_name: portainer
    image: portainer/portainer-ce:lts
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    ports:
      - 9443:9443
      - 8000:8000  # Remove if you do not intend to use Edge Agents

volumes:
  portainer_data:
    name: portainer_data

networks:
  default:
    name: portainer_network

If you terminate TLS on a reverse proxy on the same host and want Portainer listening only on localhost, change the UI publish line to "127.0.0.1:9443:9443" and keep 8000 unpublished unless Edge Agents need it. The Compose file above matches the documented portainer/portainer-ce:lts server image from Portainer’s Linux install guide.

 Portainer CE Docker Compose topology

Caption: Portainer CE runs as one server container with a named data volume, Docker socket access, HTTPS on 9443, and an optional Edge tunnel on 8000.

3. Start Portainer CE

cd /opt/portainer
docker compose -f portainer-compose.yaml up -d
docker compose -f portainer-compose.yaml ps
docker ps --filter name=portainer

You should see the portainer container healthy with 9443 (and 8000 if kept) published. Check logs if the container exits:

docker compose -f portainer-compose.yaml logs --tail=100

4. Complete Initial Admin Setup

Open a browser to:

https://YOUR_SERVER_IP:9443

or https://portainer.example.com:9443 if DNS already points at the host. Portainer ships a self-signed certificate on 9443 by default, so your browser will warn until you put a reverse proxy (or custom cert) in front.

On the initial setup page:

  1. Create the admin user and a long, unique password (store it in your password manager).
  2. Finish the wizard promptly — if you delay too long after first boot, Portainer may require a restart to re-open the creation window (see Troubleshooting).
  3. Choose to connect to the local Docker environment when prompted so the server can manage the host via the mounted socket.

You now have a working CE control plane for the local Docker engine.

5. Put HTTPS in Front with a Reverse Proxy (Recommended)

Exposing self-signed 9443 on the public internet is workable for labs but awkward for teams. Prefer terminating TLS on Caddy, Traefik, or nginx and proxying to Portainer.

Example Caddy site block (Caddyfile) that proxies to the local UI port:

portainer.example.com {
  reverse_proxy https://127.0.0.1:9443 {
    transport http {
      tls_insecure_skip_verify
    }
  }
}

If you publish Portainer only on 127.0.0.1:9443, reload Caddy after DNS for portainer.example.com points at the VPS. Confirm:

curl -I https://portainer.example.com

You can later replace the self-signed cert inside Portainer with your own certificates through Settings, but a reverse proxy is usually simpler for Let’s Encrypt automation.

Configuration

Local environment and socket access

With the Compose file above, Portainer manages the local Docker engine through /var/run/docker.sock. That mount is powerful: the Portainer process can create privileged containers. Keep the host patched, restrict who can log into Portainer, and avoid running untrusted stacks that also expose management UIs.

Optional: remove the Edge tunnel port

If you do not plan to use Edge Agents, delete the 8000:8000 mapping from Compose, recreate the container, and close the port on any public firewall rules:

cd /opt/portainer
# edit portainer-compose.yaml, remove the 8000 mapping
docker compose -f portainer-compose.yaml up -d

Users, teams, and access control

Under Users (and related access-control screens in your CE version), create non-admin users for day-to-day operators. Grant only the environments and endpoints they need. Prefer unique accounts over a shared admin password. Disable unused accounts when contractors leave.

Connecting a remote Docker Agent

To manage a second Linux Docker host, deploy the Portainer Agent on that host and register it from the server UI.

On the remote host (Docker Engine already installed):

docker run -d \
  -p 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 \
  portainer/agent:lts

Allow TCP 9001 only from the Portainer server’s IP (security group, UFW, or WireGuard), not from the whole internet.

In Portainer Server: EnvironmentsAdd environmentDocker Standalone → choose the Agent option. Enter a name and an Environment URL such as remote-docker.example.com:9001 (host and port only, no https:// scheme). Click Connect.

 Portainer server and remote agents

Caption: One Portainer Server can manage the local Docker socket and additional hosts that run the Portainer Agent on port 9001.

If you set a custom AGENT_SECRET on the server, pass the same value to each agent with -e AGENT_SECRET=…. For most new CE installs the default agent path is enough.

Usage

First login checklist

  • Open https://portainer.example.com (or :9443) and sign in as admin
  • Confirm the local environment shows running containers
  • Inspect Images, Volumes, and Networks for the host you expect
  • Create a non-admin user and verify it cannot change admin settings
  • Save the admin password and Compose path in your runbook

Deploy a test stack

From StacksAdd stack, paste a minimal Compose service (example: a tiny whoami container) or upload a Compose file from your repo. Deploy, confirm the container appears under Containers, open the published port if any, then remove the stack when finished. This validates that Portainer can create networks and containers through the socket.

Day-to-day workflows

  • Restart a failed container and stream logs during an incident
  • Pull images and recreate containers on a maintenance window
  • Prune unused images and volumes before disk fills up
  • Switch environments when you operate more than one Docker host
  • Keep production Compose in Git even when you deploy from the UI

Testing checklist

  • docker compose -f /opt/portainer/portainer-compose.yaml ps shows portainer up
  • HTTPS UI loads and admin login works after reboot
  • Local environment lists containers accurately; a test stack deploys and removes cleanly
  • Remote agent (if used) stays Connected
  • 9001 / 8000 are not open to the world without need
  • A portainer_data backup restores on a scratch host

Screenshots and Visuals

The visuals in this guide are original architecture diagrams rather than scraped product UI. They show the server and socket relationship, Compose topology, multi-host agents, and the backup path to rehearse before Portainer becomes your primary ops console.

 Portainer backup and upgrade workflow

Caption: Useful Portainer backups include the portainer_data volume plus Compose files, an off-server copy, a restore drill, and a pinned LTS image upgrade.

Troubleshooting

  • Browser certificate warning on :9443: Expected with the default self-signed cert. Use a reverse proxy with Let’s Encrypt or upload your own certificate in Portainer settings.
  • Cannot create initial admin / setup expired: Restart the container (docker compose -f portainer-compose.yaml restart) and complete admin creation promptly on first visit.
  • Container exits immediately: Check docker compose logs; common causes are port 9443 already bound or a bad volume mount. Run ss -tlnp | grep 9443 and free the port.
  • Local environment empty or permission errors: Confirm /var/run/docker.sock is mounted and the Docker daemon is running on the host (systemctl status docker).
  • Remote agent will not connect: Verify port 9001 is reachable from the Portainer server only, the agent container is running, and the Environment URL uses host:9001 without a scheme. Align AGENT_SECRET if you customized it.
  • 502 from reverse proxy: Portainer is not listening on the address you proxy to, still starting, or TLS mismatch — check docker ps and proxy upstream settings (https://127.0.0.1:9443 with insecure skip verify for the self-signed upstream).
  • Forgot admin password: Take a volume backup first, then follow current Portainer CE docs for admin password reset — do not delete portainer_data as a shortcut.
  • Snap Docker quirks on Ubuntu: Portainer advises against snap Docker (socket/path issues). Use Docker Engine from Docker’s apt repository as shown above.
  • SELinux denials (RHEL-family): Extra flags or labels may be required for the socket mount; see Portainer’s Linux notes beyond this Ubuntu path.

Scaling, Securing, and Next Steps

Back up the portainer_data volume together with /opt/portainer/portainer-compose.yaml:

cd /opt/portainer
docker compose -f portainer-compose.yaml stop

sudo mkdir -p /var/backups/portainer
docker run --rm \
  -v portainer_data:/data:ro \
  -v /var/backups/portainer:/backup \
  alpine:3.20 \
  sh -c 'cd /data && tar czf /backup/portainer-data.tgz .'

cp portainer-compose.yaml /var/backups/portainer/
rsync -a /var/backups/portainer/ backup-user@backup.example.net:/srv/backups/portainer/

docker compose -f portainer-compose.yaml start

Schedule that flow with cron or a systemd timer. Test a restore on a spare VPS: recreate the named volume from the archive, restore Compose, docker compose up -d, log in as admin, and confirm environments and stacks. Treat backup tarballs as sensitive — they contain authentication material and configuration.

For upgrades, read Portainer CE release notes, take a fresh backup, keep the :lts pin (or move deliberately), then:

cd /opt/portainer
docker compose -f portainer-compose.yaml pull
docker compose -f portainer-compose.yaml up -d
docker compose -f portainer-compose.yaml ps
docker compose -f portainer-compose.yaml logs -f --tail=100

Harden next: put the UI behind SSO or VPN if required, enroll only necessary agents, restrict 9001 and 8000, document who holds admin, and keep production Compose in Git even when you deploy through the UI.

The outcome of this guide is a private Portainer CE instance on Ubuntu 24.04 with persistent portainer_data, local Docker control, optional remote agents, HTTPS access, and a rehearsable backup path. From here, standardize stack naming, limit admin seats, automate volume backups, and track the :lts image so the control plane stays rebuildable.

Need this done on your server?

I deploy and harden Laravel, CodeCanyon, and open-source apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.