What is Portainer?
Portainer is a web interface for managing Docker, Docker Swarm, Podman and Kubernetes. Instead of typing docker commands over SSH, you see every container, image, volume and network on a dashboard, read logs, open a console, and deploy Compose stacks from a browser or a Git repository. Portainer Community Edition (CE) is open source under the zlib license and is one of the most popular first apps on a new home server.
Requirements
- Any Linux server with Docker Engine and Docker Compose v2. Portainer itself needs very little: 1 vCPU and 512 MB of RAM are plenty.
- Docker running as root, which is the default install. The Portainer docs list SELinux as a known issue; if it is enforcing on your host you need to run the container with
privileged: true. - SSH access, so you can read the setup token from the logs.
Step 1: Prepare the server
This guide assumes Ubuntu 24.04 with Docker installed from the official Docker Engine guide. Check that docker compose version prints v2, then create a folder:
mkdir -p ~/portainer && cd ~/portainer
Step 2: Create the Docker Compose file
Portainer is a single container. It needs the Docker socket to manage the host and a volume for its own database. Save this as docker-compose.yml:
services:
portainer:
image: portainer/portainer-ce:lts
container_name: portainer
restart: always
ports:
- "127.0.0.1:9443:9443"
# Only needed if you connect Edge Agents:
# - "8000:8000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
The lts tag is the long-term-support channel the official docs use. The sts (short-term support) tag gets new features first, while lts changes less often, which makes it the sane choice for a server you do not want to babysit.
The port is bound to 127.0.0.1 on purpose. Portainer has full control over Docker, which means full control over the host, so its login page should not face the internet. Docker-published ports bypass ufw, so a firewall rule alone would not protect a port published on 0.0.0.0. You reach the localhost-only port through an SSH tunnel now and through a reverse proxy in Step 4.
Step 3: Start and open the app
docker compose up -d
docker compose logs portainer | grep setup_token
Copy the token value. From your laptop, open a tunnel:
ssh -L 9443:127.0.0.1:9443 user@YOUR_SERVER_IP
Now browse to https://localhost:9443 and accept the self-signed certificate warning. Paste the setup token, then create the admin user with a password of at least 12 characters. You have 5 minutes from container start to do this; after that Portainer locks the setup for security and you need to restart the container and fetch a fresh token.
On the next screen choose Get Started to manage the local Docker environment. You can now browse containers, open logs and deploy a stack under Stacks, Add stack.
Step 4: Put it behind HTTPS
If you want to reach Portainer without an SSH tunnel, put it behind a reverse proxy with a real certificate. With Caddy on the host:
portainer.example.com {
reverse_proxy https://127.0.0.1:9443 {
transport http {
tls_insecure_skip_verify
}
}
}
The tls_insecure_skip_verify line is acceptable here because the hop is local to the server and Portainer uses a self-signed certificate on 9443. Caddy fetches a public Let's Encrypt certificate for the domain. Consider restricting the site to your own IP addresses or a VPN such as Tailscale, because an exposed Docker admin panel is a high-value target.
Backups and upgrades
Portainer's settings, users, stacks and endpoints live in the portainer_data volume. Back it up with:
docker run --rm -v portainer_data:/data -v "$PWD":/backup alpine \
tar czf /backup/portainer-$(date +%F).tgz -C /data .
Portainer also has a built-in backup under Settings, Backup configuration, which can encrypt the archive with a password. To upgrade:
docker compose pull && docker compose up -d
Troubleshooting
- "Your Portainer instance timed out for security purposes": you missed the 5-minute window. Run
docker compose restart portainer, grab the new token and finish setup immediately. - Permission denied on the Docker socket: SELinux or a rootless Docker install is blocking access. Run Docker as root or add
privileged: trueon SELinux hosts. - Browser refuses the certificate: expected on 9443. Accept the self-signed certificate, or use the Caddy setup above for a trusted one.
- Stacks created outside Portainer show "Limited" control: Portainer can list them but does not own their Compose files. Recreate them as Portainer stacks if you want to edit them in the UI.
Next steps
Deploy your next app as a Portainer stack from a Git repository so updates are one click, set up user accounts and teams, and add more servers through the Portainer Agent. Turn on the built-in scheduled backup to S3 if you use Business Edition.
Spotted something out of date? Tell us and we will update the guide.