Why install Portainer on your VPS
Managing Docker from the command line quickly becomes tedious as you accumulate containers: finding a service's logs, inspecting a volume, restarting a stack or checking resource usage requires juggling several commands. Portainer exposes all of this in a web interface: you see the status of each container, view live logs, open an interactive console, deploy stacks via a docker-compose editor and monitor CPU/RAM consumption. Unlike a PaaS such as CapRover or Dokku, Portainer does not build your code: it is a tool for administering and visualizing your existing Docker environment. It's perfect for teams who want clear, delegable access to a Docker server without giving SSH access to everyone.
What Portainer simplifies day to day
- Real-time overview of all your containers, images, volumes and networks.
- Deployment of stacks via a built-in
docker-composeeditor, without touching SSH. - Live logs and an interactive console in each container, right in the browser.
- Fine-grained rights management: create accounts and teams with limited access.
- Monitoring of the CPU, RAM and network consumption of each service.
- Management of multiple Docker hosts or Swarm clusters from a single interface.
Prerequisites for Portainer
Portainer is extremely lightweight because it only administers Docker, with no builder or heavy database. A VPS with 1 GB of RAM and 1 vCPU is more than enough to run Portainer itself; then size your VPS according to the applications you plan to host on it. Still, plan for 20 GB of SSD disk for Docker images. Docker Engine must be installed beforehand (Portainer connects to the /var/run/docker.sock socket). A domain name portainer.mydomain.com and an open port 443 are recommended to expose the interface over HTTPS. Important: Portainer gives full control over Docker, so never leave its interface directly exposed on the Internet without a reverse proxy and strong authentication. As for ports, the current installation documentation publishes 9443 (HTTPS interface with a self-signed certificate); port 8000 (TCP tunnel) is only needed for Edge agents and 9000 (HTTP) is an optional port kept from older versions. Here we only open 9443, and only for the duration of the setup.
Deploy Portainer in 5 steps
Prepare Docker on the VPS
Install Docker if it isn't already done:
curl -fsSL https://get.docker.com | sh. Check withdocker ps. Create a dedicated volume for Portainer's persistence:docker volume create portainer_data.Launch the Portainer container
Start Portainer CE:
docker run -d -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts. The official installation documentation uses thelts(long-term support) tag; that is the one we keep for a production server, rather thanlatest. The interface becomes accessible on port 9443 over self-signed HTTPS.Create the administrator account
First, read the setup token in the logs:
docker logs portainer, linesetup_token=. Since versions 2.43 and 2.39.4, a brand-new instance requires it to create the first administrator. Then openhttps://YOUR_VPS_IP:9443: the account must be created within 5 minutes of the first start, otherwise Portainer stops itself for security (message “timed out for security purposes”). In that case,docker stop portainerthendocker start portainergives you another 5 minutes. Choose a strong password (at least 12 characters).Expose it cleanly via a reverse proxy
Place Caddy or Nginx Proxy Manager in front of Portainer to serve the interface on
portainer.mydomain.comwith a real Let's Encrypt certificate. Then close direct access to port 9443. Beware: a port published by Docker bypassesufwrules (Docker diverts it before the chains ufw filters), soufw deny 9443is not enough. Recreate the container with-p 127.0.0.1:9443:9443instead, so that only the local proxy can reach it.Deploy your first stack
In the “Stacks” menu, click “Add stack”, paste a
docker-compose.ymlfile into the editor (for example an app + PostgreSQL), name the stack and click “Deploy the stack”. Portainer creates the containers, the network and the volumes, and you follow their status in real time.
Enable Portainer’s “Access Controls” to delegate the management of a specific stack to a developer without giving them access to the entire server. To go further, deploy a Portainer agent on a second VPS and attach it to your main instance: you can then administer several Docker servers from a single point. Regularly back up the portainer_data volume, which contains your users, endpoints and stack definitions, as well as the folders of Git-based stacks (compose/).
Deploy from Git with GitOps updates
Pasting a compose file is enough to get started, but a stack linked to a Git repository updates itself and keeps its history somewhere other than the interface. In “Stacks > Add stack”, choose “Git Repository”, then fill in the source, the reference (branch) and the path of the compose file. Portainer then clones the repository into its data volume, under compose/<stack id>. This mechanism, and only this one, is what the troubleshooting section below is about.
The settings to know
- Source, reference and “Compose path”: the repository, the branch and the path of the compose file from the root; “Additional paths” merges other YAML files, like several
-foptions. - “GitOps updates” by polling: Portainer checks the repository at a regular interval (“Fetch interval”).
- “GitOps updates” by webhook: Portainer generates a URL to call from your repository or a GitHub Action to trigger the update on demand.
- “Re-pull image”: pulls the images again at each update, like
--pull=always. - “Force redeployment”: redeploys at each interval or webhook, even when the repository has not changed, overwriting local changes.
- “Pull and redeploy” button on the stack page: fetches the latest commit and redeploys immediately.
Troubleshooting: “Unable to move git repository directory: Target path already exists”
Exact symptom: on a stack deployed from Git, “Pull and redeploy” (or the automatic GitOps update) fails with Unable to move git repository directory: Target path already exists, and the containers are not updated. This is issue no. 10428 of the portainer/portainer repository, opened in October 2023 on Portainer CE 2.19.1 (private GitHub repository, Edge node), described as intermittent: a reboot or waiting sometimes made it go away. It is still being reported in 2026.
State at the source, checked on 29 October 2026: the issue is still open. It carries the bug label and the “2 - High” priority, was assigned to a Portainer developer in July 2026, and its target release label moved from 3.0.0 to 3.1.0 in October 2026. No pull request is linked to it, and the only proposed fix is a commit on a user's fork that was never merged. So no official fix exists at this date: the workaround below still applies, and you should not wait for a specific version to solve this.
Causes reported by users (hypotheses, not confirmed by the vendor)
- A leftover
<id>-oldfolder incompose/: during an update, Portainer appears to rename the stack folder to<id>-oldbefore moving the new clone; if one is left over from a previous attempt, the move fails. - An interrupted deployment: a Portainer container that froze and was restarted mid-deployment, or a page refreshed during “Pull and redeploy”, left the state inconsistent for several users (the maintainer could not reproduce it that way).
- Large images: the first reporter suspected a timeout on a stack containing an image of nearly 2 GB.
- February 2026 variant: the error becomes
stat /data/compose/61: no such file or directory, meaning the stack folder has disappeared. The workaround below does not help in that case.
Careful workaround in 6 steps
Identify the stack and its ID
Note the name of the failing stack and its numeric ID, which is the name of the
compose/<id>folder. Never touch the<id>folder itself: only the<id>-oldfolder is targeted.Stop Portainer and back up the volume
Stop Portainer to freeze its database:
docker stop portainer. Then archive the whole volume:docker run --rm -v portainer_data:/data -v "$PWD":/backup alpine tar czf /backup/portainer_data-before-fix.tgz -C /data .. Check that the archive exists and is not empty before going further.List the compose folder
Display its content:
docker run --rm -v portainer_data:/data alpine ls -la /data/compose. Look for a folder whose name is the stack ID followed by-old(for example61-old).Set the leftover folder aside, without deleting it
Move it out of the volume:
docker run --rm -v portainer_data:/data -v "$PWD":/backup alpine mv /data/compose/61-old /backup/. Replace 61 with your ID. Users report that deleting this folder is enough; setting it aside has the same effect while keeping a way back.Restart and retry the update
docker start portainer, then “Pull and redeploy” on the stack. Check that the containers were actually recreated with the new version, not just that the error message disappeared.If the failure persists
If no
-oldfolder exists or if the<id>folder is missing, this workaround does not apply. The reported solution is then to delete and recreate the stack from Git. Before that, check that your application data is in named volumes and back it up. Only restore the archive from step 2 if something went wrong.
Prevention. None of these measures is a guaranteed fix, since the cause is not confirmed by the vendor: they only reduce the chances of leaving an -old folder behind. Do not refresh the page or restart Portainer during a “Pull and redeploy”. Pull large images ahead of time with docker pull to shorten the deployment. After every failed update, immediately check whether an -old folder is lying around in compose/. Back up portainer_data before updating Portainer. For a critical service, do not rely on the interface button alone: keep a fallback deployment using docker compose pull && docker compose up -d on the server. Watch issue #10428 to know when a fix arrives.
Install Portainer from the ServOrbit Marketplace
Everything above can be done manually, and knowing how is useful. If you prefer not to, Portainer CE is available on the [ServOrbit Marketplace](/applications/application-deployment-devops/portainer) and installs on your VPS in one click. The template deploys portainer/portainer-ce:latest via Docker Compose, mounts the /var/run/docker.sock socket and creates the portainer_data volume for persistence — no SSH session required for the base setup. The interface is not published on the Internet: the template binds it to the server's local address (127.0.0.1, HTTP port 9000), so you reach it through an SSH tunnel or by adding a custom domain from your client area. On first access, Portainer walks you through a setup wizard to create your admin account: if it asks for a setup token, it is in the container logs (docker compose logs, line setup_token=). Like every Marketplace template, it is provided as an installation guide, without support for the application itself. If you prefer to stay on the long-term support branch, replace the latest tag with lts in the compose file.