GuidesDevelopers and hostingPortainer container UI

Manage containers with Portainer and rootless Podman

Portainer CE on Debian 13, a web interface for your containers and Compose stacks, in rootless Podman under a user of its own behind Caddy, with its setup page locked by a token and none of its ports open to the internet.

Tested on Portainer CE 2.45.1 LTS with Podman 5.4.2 on Debian 13 (trixie) on a Melonslab server Updated September 27, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Portainer is a web interface for containers. It lists your containers, images, volumes and networks, shows their logs, starts and stops them, and deploys Compose files, which Portainer calls stacks, from a text box, an upload or a Git repository.

Portainer is mostly used with Docker, but here it runs in rootless Podman, under a user of its own called portainer, and manages that user's containers through Podman's Docker-compatible socket. With Docker, whoever controls Portainer controls the whole server, as root; with a rootless socket, a Portainer admin, and every stack, gets no more rights than the portainer user has. Portainer says rootless Podman may work but is not officially supported, so everything below was tested with it.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • Portainer CE 2.45.1 LTS started in rootless Podman, found the Podman socket as its local environment, and answered behind Caddy with a Let's Encrypt certificate, over IPv4 and IPv6. IPv6 was tested from the server itself.
  • The admin account could only be created with the setup token from Portainer's log, and a new Portainer left without an admin locked itself after 5 minutes until it was restarted.
  • A stack deployed from the web editor answered at its own name over HTTPS, and saw each visitor's real address.
  • Ports 8000, 9000 and 9443 were closed from the internet, and a backup was restored into an empty Portainer.
  • Podman's automatic update moved Portainer from 2.45.0 to 2.45.1, and everything came back by itself after a reboot.

Portainer used about 30 MB of memory, and the whole server about 550 MB.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • the firewall from step 4 of the security guide. Rootless Podman does not open ports past ufw the way Docker does, so a port a stack publishes stays closed until you allow it;
  • A and AAAA records for portainer.example.com, and for app.example.com, the app you will deploy in step 5, pointing at your server.

The examples use portainer.example.com for Portainer and app.example.com for the app. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash portainer
loginctl enable-linger portainer
machinectl shell portainer@

Everything up to step 3 runs as portainer. Switch on the user's Podman socket, which Portainer talks to, and the service that starts this user's containers at boot:

systemctl --user enable --now podman.socket podman-restart.service

podman-restart.service starts every container with the restart policy always after a reboot. The stacks you deploy from Portainer rely on it.

2. Describe the container

mkdir -p ~/.config/containers/systemd

Create ~/.config/containers/systemd/portainer.container:

[Unit]
Description=Portainer
Requires=podman.socket
After=podman.socket

[Container]
ContainerName=portainer
Image=docker.io/portainer/portainer-ce:lts
# Portainer manages this user's containers through its Podman socket.
Volume=%t/podman/podman.sock:/var/run/docker.sock
Volume=portainer-data:/data
# Only Caddy, on this server, can reach Portainer: the port is not open to the internet.
PublishPort=127.0.0.1:8097:9000
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

%t is the user's runtime directory, /run/user/ and the user's ID, where the Podman socket lives. Inside the container, it appears where Portainer expects Docker's. Portainer's plain HTTP port, 9000, is published on 127.0.0.1 only, for Caddy. Its other two ports are not published at all: 9443 serves the same interface with a self-signed certificate, and 8000 is for Edge agents on other servers, which this guide does not use.

The lts tag follows Portainer's long-term support releases. Start it, and switch on the daily update check:

systemctl --user daemon-reload
systemctl --user start portainer
systemctl --user enable --now podman-auto-update.timer

3. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

portainer.example.com {
    reverse_proxy 127.0.0.1:8097
}

Restart Caddy with systemctl --user restart caddy. Within a minute, https://portainer.example.com shows New Portainer installation.

4. Create the admin account

A new Portainer can be claimed by the first person who reaches it, so Portainer protects its setup page in two ways. It prints a new setup token in its log at every start, until an account exists, and the account cannot be created without it. And if no account is created within 5 minutes of the start, it locks itself until it is restarted.

As portainer, show the token:

podman logs portainer 2>&1 | grep setup_token
setup_token=0bcc52044f09d245edc160a14da611c58f5599c187b0381148feff1b26a94dbd

Open https://portainer.example.com, and fill in:

  • Username: admin by default. A name of your own, such as anna, is harder to guess.
  • Password and Confirm password: at least 12 characters.
  • Setup token: the value after setup_token=.

Choose Create user. On Set up Edge Compute, choose Skip, and on Welcome to Portainer, choose Get Started. Portainer has already connected the Podman socket: Home lists one environment, local, as Up, with Podman 5.4.2.

If the page says Your Portainer instance timed out for security purposes, restart Portainer as portainer with systemctl --user restart portainer, show the token again, as it is new after every start, and create the account within 5 minutes.

5. Deploy a stack

On Home, open local, then Stacks in the menu, and choose Add stack:

  • Enter app as the Name, and keep Web editor as the Build method.
  • Paste the Compose file below into the editor. It runs whoami, a small web server that shows the request it received.
  • Choose Deploy the stack.
services:
  web:
    image: docker.io/traefik/whoami
    restart: always
    ports:
      - "127.0.0.1:8100:80"

Two lines matter for every stack you deploy this way:

  • 127.0.0.1: in front of the port keeps it for Caddy. Without it, the port listens on every address, and ufw keeps it closed until you allow it.
  • restart: always lets podman-restart.service start the stack again after a reboot. It starts only containers with always, not unless-stopped.

Then give the app its name. As caddy, add this block to ~/Caddyfile, and restart Caddy:

app.example.com {
    reverse_proxy 127.0.0.1:8100
}

https://app.example.com shows whoami's page. The visitor's own address is in its X-Forwarded-For line, as Caddy passes it on.

To change a stack, open it, choose Editor, edit the file, and choose Update the stack. Turn on Re-pull image and redeploy in the question that follows to fetch newer images at the same time.

The containers of every stack run as the portainer user. A stack that mounts a directory of the server, such as /etc, can only read and change what that user can.

6. What Portainer contacts

Portainer CE 2.45 has no setting for anonymous statistics any more: older guides mention Allow collection of anonymous statistics, which is gone, and Portainer's own code has no statistics left in it. It still makes these requests, none of which carries data about your containers:

  • The server asks GitHub for the latest Portainer release, to tell you when an update is out.
  • The server fetches the news shown on Home from Portainer's file server, and the list under Templates from GitHub when you open it.
  • Your browser loads images for the news and the templates' logos from Portainer's file server.

7. Back up

Portainer's own backup holds its users, environments, settings and the Compose files of your stacks. Open Settings, and under Back up Portainer:

  • Turn on Password Protect, and enter a Password. The backup contains your stacks' environment variables and any registry passwords, so do not skip it.
  • Choose Download backup. The browser saves a file called portainer-backup_ with the date and .tar.gz.encrypted.

Keep the file, and its password, somewhere safe, away from the server. To restore it, on a new server or after starting Portainer with an empty volume, choose Restore Portainer from backup on the first page instead of creating an account. Choose Select file, enter the backup's Password and the Setup token from the log, and choose Restore Portainer. Then log in with an account from the backup.

The backup does not contain the data inside your stacks' volumes. As portainer, podman volume ls lists them, and podman volume export saves one to a file, as in the Uptime Kuma guide. Stop the stack first under Stacks for a consistent copy.

8. Updates

Once a day, Podman checks for a newer lts image of Portainer, and restarts it if there is one. To see what it would do right now, run podman auto-update --dry-run as portainer. After an update, or any restart of Portainer, you log in again. The version you run is shown at the bottom of the menu.

The automatic update covers Portainer itself. Your stacks' images are updated when you redeploy them with Re-pull image and redeploy, as in step 5.

Troubleshooting

The page says your Portainer instance timed out. No account was created within 5 minutes of the start. Restart Portainer with systemctl --user restart portainer as portainer, and use the new setup token.

The setup token is refused. Portainer was restarted after you copied it, and every start makes a new one. Run the podman logs command from step 4 again.

A stack is not running after a reboot. Its Compose file needs restart: always, and podman-restart.service must be on: as portainer, systemctl --user is-enabled podman-restart.service should print enabled.

Portainer asks you to log in again. Portainer was restarted, by an update or a reboot. That ends every session.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides