Portainer is a web UI for Docker. It shows containers, images, volumes and networks, and it can deploy whole Compose projects as stacks, including straight from a Git repository. The portainer/portainer repository had about 38,700 GitHub stars on October 21, 2026, and the portainer/portainer-ce image had passed 1.5 billion pulls on Docker Hub. The current Community Edition (CE) release is 2.45.2 LTS, published on October 8, 2026.
This guide installs Portainer CE with Docker Compose, gets past the five-minute admin setup timeout, moves an app that was started with plain docker compose into Portainer without losing its data, and sets up a stack that redeploys itself when you push to Git. It ends with a backup and an upgrade from 2.45.1 to 2.45.2.
TL;DR:
- Run
portainer/portainer-ce:2.45.2with the Docker socket and aportainer_datavolume, and publish only127.0.0.1:9443. Port 8000 is only for Edge Agents. - Create the admin user within 5 minutes of the first start, using the
setup_token=value fromdocker logs portainer. If the timeout hits, rundocker restart portainerand use the new token it prints. - Apps started with
docker composeshow as Limited. Rundocker compose downwithout-v, then deploy the same file as a Portainer stack with the same name, and the stack gets Total control while keeping its volumes. - For Git stacks, turn on polling on the Git source, or turn on Create a Webhook on the stack. In CE 2.45 both redeployed the stack after a push.
- Before an upgrade, download a backup from Settings, then bump the image tag and recreate the container. Your stacks keep running.
Install Portainer CE with Docker Compose
The compose file and ports
The official Linux install page uses the lts tag and publishes ports 9443 and 8000 on all interfaces. The version below pins the tag and binds the UI to localhost, so nothing is reachable from the network until you put a reverse proxy in front:
name: portainer
services:
portainer:
container_name: portainer
image: portainer/portainer-ce:2.45.2
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
ports:
- 127.0.0.1:9443:9443
volumes:
portainer_data:
name: portainer_data
networks:
default:
name: portainer_network
Start it with docker compose up -d. The ports work like this:
- 9443 serves the UI and API over HTTPS with a self-signed certificate that Portainer creates on first start. The generated certificate was valid for five years.
- 9000 is plain HTTP. The container still listens on it, and
http://portainer:9000answered from another container on the same network, but the docs only add-p 9000:9000"for legacy reasons". Do not publish it. - 8000 is the tunnel server for Edge Agents. The docs call it optional, so leave it out unless you manage remote Edge environments.
Portainer team member jamescarppe answered the port question in issue #10797: "Port 8000 has never been used for the Portainer UI - 8000 is used for the Edge Agent tunnel exclusively."
The socket mount gives Portainer full control of Docker on the host. Treat the UI like root access. For remote access, put it behind HTTPS with a proxy such as Nginx Proxy Manager or keep it on a VPN. The reverse proxy docs warn that a proxy that takes HTTP from the browser but forwards to 9443 breaks login, because Portainer then sets Secure cookies that the browser drops. Forward the Host header as the browser sent it, which in nginx means $http_host rather than $host.
First login and the setup token
Open https://127.0.0.1:9443 and accept the certificate warning. The setup page asks for a username (default admin), a password of at least 12 characters and a setup token. Since versions 2.43 and 2.39.4, new instances require that token and print it to the log:
docker logs portainer 2>&1 | grep setup_token
The setup token FAQ lists the flags to change this: --setup-token <value> sets your own, --admin-password-file pre-sets the admin password so no token is needed, and --no-setup-token turns the check off, which the docs reserve for networks you fully control. After the admin user exists, Portainer asks about Edge Compute (Skip is fine for a single host) and then opens the environment wizard with your local Docker already connected.
Fix the admin setup timeout
What the timeout looks like
A new Portainer instance gives you 5 minutes to create the admin user. The timeout FAQ explains it as protection against someone else claiming a fresh install. The 5 minutes count from container start. In the log below, the HTTPS server started at 02:11:47 UTC and the timeout line appeared at 02:16:47 UTC, five minutes later:
The container does not exit. docker ps still shows it as Up and /api/status still answers, so it looks healthy from the outside. The browser shows a New Portainer installation card that says the instance timed out for security purposes, and the API refuses to create the admin with Administrator initialization timeout.
Restart and finish within five minutes
Restart the container and the clock starts again:
docker restart portainer
docker logs portainer 2>&1 | grep setup_token
The restart prints a new setup token, so copy it again instead of reusing the one from the first start. The docs also mention that in some cases you need docker rm portainer and a fresh docker run or docker compose up -d. If the timeout shows up on an instance that already had an admin, the same FAQ points at the data volume: if portainer_data is not mounted, Portainer thinks it is a new install.
Move existing compose projects into Portainer
Why the stack shows Limited
Portainer lists every Compose project it finds on the host, but it can only edit the ones it deployed. Here Uptime Kuma 2.5.6 was started with plain docker compose up -d from its own folder, as in our Uptime Kuma setup guide:
services:
uptime-kuma:
image: louislam/uptime-kuma:2.5.6
restart: unless-stopped
volumes:
- kuma-data:/app/data
ports:
- 127.0.0.1:3001:3001
volumes:
kuma-data:
In the Stacks list it appeared as uptime-kuma with Control set to Limited, and its details page said so:
A Limited stack has no Editor tab, no stack-level stop or delete, no environment variables and no duplicate or migrate option. You can still start, stop, restart and inspect its containers. Portainer cannot fix that by itself because it never had the compose file. jamescarppe explained it in portainer-docs issue #75: "The \"limited\" state displays when a stack is deployed outside of Portainer." The same thread notes that Portainer itself will always show as Limited, since it cannot deploy itself.
Take full control without losing data
The fix is to redeploy the same compose file through Portainer, under the same name. Compose prefixes named volumes with the project name, and Portainer uses the stack name as the project name. So a stack called uptime-kuma finds the existing uptime-kuma_kuma-data volume.
- In the old project folder, run
docker compose down. Do not add-v, which deletes the named volumes. - In Portainer, open Stacks, then Add stack, and set Name to exactly the old project name (
uptime-kuma). - Choose Web editor or Upload and use the same compose file. Load the old
.envwith Load variables from .env file if the project had one. - Click Deploy the stack.
After the redeploy, the stack showed Total control, an Editor tab and stop and delete buttons. Docker listed the same uptime-kuma_kuma-data volume, and Uptime Kuma opened on its login page instead of the first-run setup, so the admin account and data had survived. The same steps work for any compose project that keeps its data in named volumes, as long as the stack name matches the old project name.
Relative bind mounts do not carry over. Portainer stores each stack's compose file under /data/compose/<stack id> inside its own container, and Compose resolves ./data against that folder. A stack with ./data:/d got the host path /data/compose/3/data as its mount source, a new empty directory, not the old project folder. A project like the Stirling-PDF stack, which keeps everything under ./stirling-data, needs absolute host paths in its compose file before you redeploy it in Portainer. Relative paths for Git stacks were requested in issue #6390 and shipped in 2.17 as a Business Edition option, and the Add a new stack docs still list relative path volumes as Business Edition only.
Deploy stacks from Git and keep them updated
Add a Git source and deploy the stack
In 2.45, Git stacks start from a source. Open Sources in the left menu, click Add new, and enter a name and the repository URL. Turn off Authentication for a public repository, or add a username and token for a private one. Turn on Enable polling and set a Fetch interval such as 5m. The form tests the connection before you can continue. Portainer clones the repository from inside its own container, so use a URL the container can reach. Here a Forgejo 17.0.0 server on the same Docker network served http://forgejo:3000/labadmin/whoami-stack.git and the form reported Connection successful.
The repository held one small compose file:
services:
whoami:
image: traefik/whoami:v1.12.0
command: --name ${APP_NAME:-whoami}
ports:
- 127.0.0.1:8082:80
restart: unless-stopped
Then go to Stacks, Add stack, pick Repository, select the source, keep refs/heads/main as the reference and set Compose path to compose.yml. Environment variables can be set in the form, but a stack.env file referenced from the compose file must already be in the repository, as the form itself points out.
After the deploy, the stack page shows a Managed by Git panel with the repository, reference, file, current commit, polling interval and webhook URL, plus Pull and redeploy, Edit stack settings and Detach from Git buttons. Detaching is one way: the stack keeps only its main compose file and cannot go back to Git.
Polling and webhooks in CE
Both update paths worked in CE 2.45.1:
- Polling: with a 1-minute fetch interval on the source, a commit that changed the image from
traefik/whoami:v1.11.0tov1.12.0was running 36 seconds after the push. The delay can be up to one full interval plus the redeploy. In 2.45 the interval belongs to the source, not the stack. - Webhook: with Create a Webhook on, a commit followed by an unauthenticated
curl -X POSTto the webhook URL returned HTTP 204, and the container served the new name 1.5 seconds later. A made-up webhook ID returned 404.
The webhook URL works without a login, so treat it like a password and only paste it into your Git server or CI secrets. Two options next to it carry a Business Feature badge in CE: Re-pull image, which pulls fresh images on every update, and Force redeployment, which overwrites local changes even when Git has not changed.
Portainer's own pages disagree about what CE gets. The features page lists "GitOps automatic updates and webhooks" among Business Edition features, and the January 2026 CE vs BE post sums up CE GitOps as "Basic Git deployment". The Add a new stack docs describe polling and webhook updates for Git stacks with no Business Edition flag, and both worked in CE 2.45. The separate stack webhooks page, which the web editor and upload options link to, is marked Business Edition only. Since Portainer markets these as Business Edition features, check the toggles after each upgrade before you build a deploy pipeline on them.
To get a new image in CE, change the tag in Git instead of reusing a moving tag like latest. Pinned tags are the safer habit for any stack.
Back up, upgrade and keep it private
Back up the configuration
In Settings, under Back up Portainer, Download backup file is available in CE. Store in S3 carries a Business Feature badge, and the settings docs list scheduled local backups as Business Edition too. The download here was a 38 KB tar.gz with portainer.db, the compose files of both Portainer stacks, the TLS certificate and key and the Edge tunnel private key. Turn on Password protect before you download it, and store it like a secret.
The backup FAQ is clear that this covers Portainer configuration only, not containers, images, volumes or the data inside them. Back up app data separately. For a full copy of Portainer itself, stop it and archive the volume:
docker compose down
docker run --rm -v portainer_data:/data:ro -v "$PWD/backup:/backup" alpine:3.24.2 tar czf /backup/portainer_data-2.45.1.tar.gz -C /data .
Upgrade to a new version
The Docker upgrade docs stop and remove the container, pull the new image and start it again with the same volume. With Compose, change the tag and recreate. This install went from 2.45.1 to the 2.45.2 tag shown in the compose file above:
sed -i "s/portainer-ce:2.45.1/portainer-ce:2.45.2/" compose.yml
docker compose pull
docker compose up -d
On start, 2.45.2 copied the database to /data/backups/portainer.db.bak, logged migrating database from version 2.45.1 to 2.45.2 and then db migrated to 2.45.2. The Uptime Kuma and whoami containers kept running the whole time, both stacks still showed Total control, and the next Git push was live within 46 seconds through polling. The rollback FAQ explains why the database copy matters: a newer database will not start on an older Portainer, so a rollback means restoring portainer.db.bak and starting the old image tag.
Portainer itself is light. At idle on 2.45.1 it used 27 to 34 MiB of RAM and close to 0% CPU. To show it on a start page, Homepage has a Portainer widget. Keep the UI on localhost or a private network, and only expose it through HTTPS with access limited to people who should have root on the host.
FAQ
Setup and access
What does "Your Portainer instance has timed out for security purposes" mean?
You did not create the admin user within 5 minutes of the first start. Restart the container with docker restart portainer, get the new setup token from docker logs portainer, and finish the form within 5 minutes. Users reported the same log line in issue #8457, and the official FAQ gives the restart fix.
Do you need ports 8000 and 9000?
No. The UI and API run on 9443 over HTTPS. Port 8000 is only for Edge Agents and port 9000 is plain HTTP that the docs keep for legacy setups. A user in issue #10797 lost access on 9000 after an upgrade because the new default command no longer publishes it.
Where is the setup token?
In the container log, on the line that starts with setup_token=. Run docker logs portainer 2>&1 | grep setup_token. The setup token FAQ covers the flags that set or skip it.
Stacks and Git
Why do stacks show as Limited, and how do you fix it?
Portainer did not deploy them, so it has no compose file to edit. Run docker compose down without -v in the project folder and deploy the same file as a Portainer stack with the same name. A user in issue #12348 could not delete or edit Limited stacks after reinstalling Portainer and fixed it by redeploying every compose file.
Are Git webhooks and automatic updates free in Portainer CE?
For Git stacks in CE 2.45, yes: polling on the Git source and the Create a Webhook toggle both redeployed the stack after a push, and polling kept working after the upgrade to 2.45.2. Re-pull image and Force redeployment carry a Business Feature badge, and the docs mark webhooks on non-Git stacks as Business Edition. Portainer's features page still lists GitOps automatic updates and webhooks as Business Edition, so check the toggles in your version before you rely on them.
Does a Portainer backup include my containers and volumes?
No. The backup FAQ says it covers Portainer settings, users, environments, stack definitions and credentials, not containers, images, volumes or app data. Back up app volumes on their own.





Top comments (1)
Before the migration's
docker compose down, I'd record the running container's actual volume names and mount destinations, then compare them after the Portainer deploy. Matching the directory name alone can miss an earlier-porCOMPOSE_PROJECT_NAMEoverride. Docker documents that precedence here.A disposable regression I'd try: create a named volume with a sentinel file under a custom project name, migrate it, then check both the mounted volume identity and the sentinel. Repeat with a deliberately mismatched stack name. An empty first-run screen should be caught as a failed migration even though the original volume still exists.
This is a proposed check. I haven't run that Portainer migration.