DEV Community

DeployLark
DeployLark

Posted on Originally published at deploylark.com

Portainer CE on Docker: Install, Stacks and Git Updates

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.2 with the Docker socket and a portainer_data volume, and publish only 127.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 from docker logs portainer. If the timeout hits, run docker restart portainer and use the new token it prints.
  • Apps started with docker compose show as Limited. Run docker compose down without -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
Enter fullscreen mode Exit fullscreen mode

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:9000 answered 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
Enter fullscreen mode Exit fullscreen mode

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:

Terminal capture from Portainer CE 2.45.1. docker logs -t portainer shows starting Portainer version 2.45.1 and starting HTTPS server on port 9443 at 02:11:47, then at 02:16:47 the line the Portainer instance timed out for security purposes, to re-enable your Portainer instance, you will need to restart Portainer. A POST to /api/users/admin/init returns the JSON message Administrator initialization timeout. After docker restart portainer, the log prints a different setup token, hidden here, and the admin form must be finished within 5 minutes of the restart.

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

In the Stacks list it appeared as uptime-kuma with Control set to Limited, and its details page said so:

Portainer CE 2.45.1 stack details page for uptime-kuma. An information box says This stack was created outside of Portainer. Control over this stack is limited. There is no Editor tab and no stop or delete stack button. The containers table lists uptime-kuma-uptime-kuma-1, healthy, image louislam/uptime-kuma:2.5.6, published port 3001.

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.

  1. In the old project folder, run docker compose down. Do not add -v, which deletes the named volumes.
  2. In Portainer, open Stacks, then Add stack, and set Name to exactly the old project name (uptime-kuma).
  3. Choose Web editor or Upload and use the same compose file. Load the old .env with Load variables from .env file if the project had one.
  4. 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.

Diagram in two parts. Part 1, move a plain compose project into Portainer: the Stacks list shows uptime-kuma with Control Limited and no Editor tab, then docker compose down runs in the old folder without -v so the volume stays, then Add stack in Portainer with name uptime-kuma and the same compose file, ending at Control Total with the same volume uptime-kuma_kuma-data reused and the admin login kept. Part 2, keep a stack updated from Git: a git push to main on Forgejo 17.0.0 reaches Portainer either through polling, a source setting with a 1 minute fetch interval that applied the change in 36 seconds, or a webhook, a stack setting where a POST to the stack URL applied it in 1.5 seconds. Both end in a redeploy where Portainer pulls the commit and runs docker compose up for the stack whoami. A note says the Re-pull image and Force redeployment options carry a Business Feature badge in CE and that Portainer itself stays Limited.

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
Enter fullscreen mode Exit fullscreen mode

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.

Portainer CE 2.45.1 Create stack form with the Repository build method. Source is set to whoami-stack, Repository reference is refs/heads/main and Compose path is compose.yml. The Create a Webhook toggle is on and the Webhook URL is hidden in this screenshot. A warning says local changes will be overwritten by the git repository content. The Re-pull image and Force redeployment toggles are off and each carries a Business Feature badge.

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.0 to v1.12.0 was 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 POST to 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 .
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 Community Edition 2.45.2 LTS Stacks list after the upgrade. gitsrv and portainer, both started with docker compose, show Control Limited. uptime-kuma, created 2026-10-21 10:18:59 by admin, and whoami, created 10:23:58 by admin and updated 10:29:20, show Control Total.

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)

Collapse
 
jaredchuvn profile image
Jared Chu •

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 -p or COMPOSE_PROJECT_NAME override. 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.