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:ltstag 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
8000is 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_datavolume - 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/AAAArecords 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
:ltstag over floating:lateston 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
9443to 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.
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:
- Create the admin user and a long, unique password (store it in your password manager).
- 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).
- 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: Environments → Add environment → Docker 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.
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 Stacks → Add 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 psshowsportainerup- 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/8000are not open to the world without need- A
portainer_databackup 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.
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 port9443already bound or a bad volume mount. Runss -tlnp | grep 9443and free the port. - Local environment empty or permission errors: Confirm
/var/run/docker.sockis mounted and the Docker daemon is running on the host (systemctl status docker). - Remote agent will not connect: Verify port
9001is reachable from the Portainer server only, the agent container is running, and the Environment URL useshost:9001without a scheme. AlignAGENT_SECRETif you customized it. - 502 from reverse proxy: Portainer is not listening on the address you proxy to, still starting, or TLS mismatch — check
docker psand proxy upstream settings (https://127.0.0.1:9443with 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_dataas 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