What you get, and the one catch
Portainer gives you a web page for Docker. Instead of typing commands to see what is running, restart a stuck container or read its logs, you click. The Community Edition (CE) is free, and the whole server side is one container plus one storage volume called portainer_data.
The catch is power. To manage Docker, Portainer is given Docker's control socket, /var/run/docker.sock, and Docker runs as root. Put plainly, and this is our own reading rather than a line from the docs: whoever logs in to Portainer as an administrator can start a container that sees the server's entire disk, so that login is as strong as the root password. Portainer's own security page agrees in its way: it won't stop a permitted user from doing anything Docker allows, and how you expose it is your call. Every step below follows from that.
The plan, before you type anything
- Switch the firewall on, allowing only SSH.
- Open an SSH tunnel from your laptop with your own login.
- Start Portainer with its page tied to the server's loopback address, 127.0.0.1.
- Create the admin through the tunnel, inside the 5-minute window.
- Only then decide whether your team needs it outside SSH.
The order matters because a brand-new Portainer is waiting to be claimed. Starting with Portainer 2.43 and 2.39.4, a setup token makes that harder for a stranger, and the five-minute limit stops a forgotten install from sitting open. Keeping the page private while you claim it removes the race entirely.
Will a small server do?
Yes, as far as Portainer goes: its docs give no processor or memory requirement for it, so the apps you run inside Docker decide the size. Count their memory first. Two things the docs do mention: deploying stacks from Git clones each repository into Portainer's volume, which can eat disk, and bigger installs should sit on SSD-class storage. Portainer ships for x86_64 and ARM64 processors.
- A few apps for yourself: a small cartridge server. The m510 and m710x are the cartridges with a console screen; the others give you power control. The X-Gene ARM cartridge won't do: its only choices are Ubuntu 16.04 and 18.04, far older than the Docker this guide checks for.
- A business app with its database: a single-processor rack server, which brings its own console for the day you firewall yourself out by mistake.
- Lots of stacks at once: a two-processor machine, so containers stop competing for cores.
Browse them on our dedicated servers page. Every one hands you the entire machine and root, a port nobody meters, and operating system reinstalls you start yourself in the control panel. Ready servers are with you within 30 minutes of approval; you manage the software, and we keep the hardware and the network running. If one host turns into several, Kubernetes on our servers keeps your root on every machine while we run the control plane.
Install it so only you can reach it
Install Docker Engine using Docker's official packages first; Portainer's guide warns against the snap version on Ubuntu. On your laptop, open a second terminal and start the tunnel, then leave it open: ssh -N -L 127.0.0.1:9443:localhost:9443 you@your-server. Use your own account for it. Then run sudo -i on the server and paste this:
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw \
&& ufw allow OpenSSH && ufw --force enable \
&& ENGINE=$(docker version --format '{{.Server.Version}}') \
&& [ "${ENGINE%%.*}" -ge 28 ] && echo "engine $ENGINE is new enough" \
&& docker volume create portainer_data \
&& docker run -d --name portainer --restart=always \
-p 127.0.0.1:9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data portainer/portainer-ce:lts \
&& { sleep 5; docker logs portainer 2>&1 | grep setup_token \
|| echo "Portainer is running but no token yet: run docker logs portainer | grep setup_token"; } \
|| echo "something above failed; nothing more was done"
We changed two things in Portainer's official command. Its page listens on 127.0.0.1 only, and port 8000 is gone, because that port exists for Edge agents on other sites and the docs call it optional. If you prefer Compose, Portainer's sample file works the same way once its ports read 127.0.0.1:9443:9443 and the 8000 line is removed.
Why the Docker version check: publish a port with no address in front and Docker opens it on all of the server's addresses, 0.0.0.0 and [::] alike. Its own rules then bypass ufw for IPv4, and for IPv6 too once Docker's IPv6 networking is switched on. Docker's own documentation cautions that on releases older than 28.0, other hosts sharing the local network segment can connect to a port published only to localhost, so the block stops on anything older.
Claim the admin account before the clock runs out
The five-minute clock begins at the container's first start, so go straight to your browser. With the tunnel still open, visit https://localhost:9443. Your browser will complain about the certificate: Portainer makes its own self-signed one, and on a tunnel only you use, that is fine.
- The setup page asks for the token. Copy what follows
setup_token=in the last line the install printed. It is single use, so you never need it again. - Pick an admin name (admin is suggested) and a password of 12 characters or more.
- When asked about Edge Compute, choose Skip unless agents on other sites will dial in. Portainer then finds the Docker on this server by itself.
- Too slow? Portainer switches its service off, so the browser gets no answer. Run
docker restart portainer, then use the latestsetup_token=line thatdocker logs portainershows, and you have five fresh minutes.
Portainer also lets you set the first password from a file at start-up, or turn the token off on networks you fully control. You need neither while the page is private.
Letting your team in without SSH
Staying on the tunnel is the safest choice. If others need the page directly, Portainer documents two ways, and both need care.
Give Portainer a real certificate. Start it again with --sslcert and --sslkey pointing to your certificate and key in PEM format, with the whole certificate chain included; or upload both in its Settings under SSL certificate. A Certbot certificate needs both its live and archive folders mounted read-only, because the docs note Docker has trouble with the links between them. Then publish 9443 publicly and run ufw allow 9443/tcp too: IPv6 visitors still pass through ufw unless Docker's IPv6 networking is on.
Put a reverse proxy in front. The proxy must pass the visitor's Host header along unchanged, tell Portainer the real scheme with X-Forwarded-Proto, and, if it changes the hostname, you add --trusted-origins with your public address. Get the scheme wrong, say HTTP from the browser but HTTPS onward to Portainer, and nobody can log in: the secure cookie gets thrown away and the answer is a 401. Portainer also refuses to appear inside an iframe. Our guide to Nginx Proxy Manager shows one proxy serving several apps.
Before you share logins, know what Community Edition can and cannot limit. Users are either administrators or not; named roles such as Operator, Helpdesk and Read-only belong to role-based access control, which the docs reserve for Business Edition. You can still restrict each container, stack or volume to chosen users or teams, and keep the environment's security settings that hide bind mounts, privileged mode, host devices and similar options from non-admins, which are on by default. Portainer's docs say plainly that no list of settings can stop every capability a container might be granted, that a container sharing host PID 1 lets its user execute anything as root on the server, and that a mapped device gives full access to that device. Give admin rights only to people you would trust with root.
Saving Portainer's settings, and getting them back
Community Edition has one built-in option: in Settings, under Back up Portainer, download a backup file to your browser, with a password if you like. It holds Portainer's internal database plus the Compose files of stacks deployed through it. Only Business Edition can schedule backups or send them to S3 and Azure Blob, and that file can only be restored into a brand-new Portainer during first setup, token and five minutes included. It never contains your apps, their images, or anything your apps saved in volumes, so those need backups of their own.
We like a second copy you control: the whole portainer_data volume, packed while Portainer is stopped. Your other containers keep running while it is down. Paste as root, then move the file somewhere off the server:
set -o pipefail
install -d -m 700 /root/portainer-archive
VOL=$(docker volume inspect -f '{{.Mountpoint}}' portainer_data) \
&& OUT=/root/portainer-archive/data-$(date +%F-%H%M).tar.gz \
&& docker stop portainer \
&& tar czf "$OUT" -C "$VOL" . \
&& tar -tzf "$OUT" >/dev/null && echo "saved and readable: $OUT"
docker start portainer
To go back to one of those archives, become root (sudo -i) and put its name in FROM below. The safety copy gets a name down to the second, and the block will not overwrite an existing one. Before anything is deleted, the block packs the current state into a separate file and checks it can be read; if any step fails, it stops there. Changed your mind afterwards? Run it again with FROM set to the file it called the safety copy.
set -o pipefail
FROM=/root/portainer-archive/data-YYYY-MM-DD-HHMM.tar.gz
VOL=$(docker volume inspect -f '{{.Mountpoint}}' portainer_data)
KEEP=/root/portainer-archive/safety-$(date +%F-%H%M%S).tar.gz
[ ! -e "$KEEP" ] && [ "$KEEP" != "$FROM" ] \
&& tar -tzf "$FROM" >/dev/null \
&& docker stop portainer \
&& tar czf "$KEEP" -C "$VOL" . && tar -tzf "$KEEP" >/dev/null \
&& echo "safety copy: $KEEP" \
&& find "$VOL" -mindepth 1 -delete && tar xzf "$FROM" -C "$VOL" \
&& docker start portainer && echo "back to $FROM" \
|| echo "a step failed: if 'safety copy:' was printed and the volume was emptied, run this again with FROM set to that file; otherwise nothing was deleted, so run: docker start portainer"
Keeping Portainer up to date
Upgrading means replacing the container while the volume stays put; on its next start Portainer updates its own database. Take a backup first, as the docs advise. Their order is stop, remove, pull, run. We fetch the image first, so a download that fails leaves your current Portainer running: pull portainer/portainer-ce:lts, and only when that works remove the old container with docker rm -f portainer (the volume stays), then the same docker run lines as in the install, with 9443 still on 127.0.0.1. The lts tag follows Portainer's long-term-support releases, and any agents you add later need the server's exact version.
Community or Business Edition, and the licence
Portainer is honest about where CE belongs. Its GitHub README describes it as made for home labs, learning and personal projects, not for production, offered without official support, and steers businesses running production to Business Edition, which adds features such as role-based access, GitOps, edge management, audit and single sign-on. Another FAQ explains that BE-only features pay for CE's development. You can move from CE to BE later and keep your configuration, and Portainer lists BE's plans on its own website.
The code itself is under the zlib licence, per the LICENSE file. This is how we read it in general terms, and it is not legal advice: a permissive licence, use and change it freely, commercially too, as long as you don't claim you wrote it, you mark modified versions as modified, and you keep the notice in any source you pass on. Nothing in it asks you to release your own code.
Questions people ask
- Is Portainer CE free for my business to use?
- The zlib licence lets anyone use it for any purpose, including commercial work, as we read it in general terms (this is not legal advice). Portainer's README, though, describes CE as a non-production tool with no official support, and it sells Business Edition for production. Weigh both before you rely on it.
- Do I have to open port 8000?
- Only if Edge agents on other networks will connect to this Portainer. For a server managing its own Docker, the docs call 8000 optional, and in the setup here only 9443 is used, on the server's loopback address.
- I already run containers with docker compose. Will Portainer see them?
- Yes. Portainer lists everything Docker is running and marks what it did not start as external, which gives you fewer controls over it. Those containers are visible to administrators only until you add Portainer's access-control labels to them.
- Missed the five-minute setup window. Now what?
- Portainer shuts its own service down, so the setup page stops loading. Restart the container, use the most recent setup_token= entry its log shows, and finish the admin account within the new five minutes.
- Does Portainer's backup include my apps' data?
- No. Its backup file and the archive described above cover Portainer's own settings, users and stack definitions. Images, containers and the data your apps keep in volumes or bind mounts need their own backups.