How To Install Portainer for Docker Container Management on Ubuntu
Tricknowtech Team 9 min read
Goal
In this tutorial, you will install Portainer Community Edition as a Docker container, restrict and secure access to its web-based UI, complete the first-run admin setup, and verify it can manage your host's Docker environment by deploying a test stack.
Prerequisites
A server running Ubuntu 22.04 LTS or 24.04 LTS with a non-root sudo user
Docker Engine already installed and running on that server
UFW installed and enabled, with SSH access already allowed so you don't lock yourself out
Basic familiarity with the docker CLI (docker ps, docker run) and with docker-compose-style YAML
Let an AI agent do this for you
Copy a ready-made prompt for an AI coding assistant with terminal access to your server (Claude Code, Cursor, or similar) — it can carry out the steps below for you. Review what it plans to run before it executes anything.
Portainer is a web-based UI for Docker: a dashboard where you can start and stop containers, pull and remove images, inspect volumes and networks, and deploy multi-container applications (called "Stacks" in Portainer, functionally equivalent to applying a docker-compose.yml) without typing docker CLI commands for routine work. This tutorial installs Portainer Community Edition (CE) as a container on a host that already has Docker running, secures access to its admin UI, and confirms it that can actually manage the host's Docker environment.
Step 1 — Create a Persistent Volume for Portainer's Data
Portainer stores its own configuration — user accounts, settings, endpoint definitions — inside its container's filesystem at /data. If you ever recreate the container (for an upgrade, for example), anything written directly into the container is lost. A named Docker volume keeps that data outside the container's writable layer so it survives recreation.
bash
docker volume create portainer_data
Step 2 — Run the Portainer Container
Start Portainer itself, using its official image and the standard quick-start flags:
“The Docker socket mount is the important line to understand, not just copy. /var/run/docker.sock is effectively a root-access API for the host: anything with access to it can create a container that mounts the host's root filesystem, which is equivalent to root shell access on the machine. By mounting it, Portainer — and therefore anyone who can log into Portainer's UI — has root-equivalent control over the entire server, not just the containers it displays. Treat Portainer's admin login with the same seriousness as root SSH access: a strong, unique password, and network access restricted to only the people who need it (Step 3).”
Step 3 — Allow the Portainer UI Through the Firewall
If UFW is active (check with sudo ufw status), you need a rule for port 9443 or the browser connection will just hang or refuse. Because of the socket-access risk above, treat 9443 the way you'd treat a database's admin port rather than a public web port: open it to everyone only if you genuinely need to reach the UI from anywhere, and prefer restricting it to known source IPs.
bash
# Open to any source (simplest, but exposes the login page to the internet)
sudo ufw allow 9443/tcp
# Or, restrict to a specific trusted IP or office/VPN range instead
sudo ufw allow from 203.0.113.10 to any port 9443
203.0.113.10 above is a placeholder — replace it with your own admin workstation's or VPN's actual public IP. This is the same IP-restriction pattern used for locking down any other administrative port: allow the specific source, not 0.0.0.0/0. Confirm the rule is active before moving on:
bash
sudo ufw status verbose
Step 4 — Complete the Initial Admin Account Setup
In a browser, go to https://your_server_ip:9443 (using your actual server IP in place of the placeholder). Your browser will warn that the certificate isn't trusted — that's expected on first run, since Portainer generates a self-signed certificate for itself. It's acceptable to proceed past the warning for this one-time initial setup step, but don't leave the UI running on a self-signed certificate long-term; see the note below.
Portainer only accepts the initial admin-account creation for a short window after the container starts, as a security measure against someone else reaching an unclaimed instance first. Complete this step promptly: choose a username and a strong, unique password, and submit the form. If you take too long and the window closes before you finish, that's not a problem — restarting the container opens a fresh setup window, and nothing is lost since no configuration existed yet to lose. See the Troubleshooting section below for the exact command, and for a separate, less common failure mode that does need a different fix.
Using a real TLS certificate instead of the self-signed one
For anything beyond quick local testing, put Portainer behind Nginx as a reverse proxy and issue it a real certificate with Certbot, the same way you would for any other internally-hosted admin panel — see the earlier Nginx reverse proxy and Certbot/Let's Encrypt tutorials in this series for the general setup. Proxy to Portainer's port 9443 (or run Portainer on a non-TLS internal port and let Nginx terminate TLS) rather than exposing the self-signed listener directly to the internet.
Step 5 — Connect to the Local Docker Environment
After creating the admin account, Portainer shows a "Get Started" screen. Because the container has the Docker socket mounted, Portainer already detects the host's Docker daemon as an available environment — click Get Started (or select the local environment) to connect to it. No further configuration is needed for managing this host.
Step 6 — Verify Portainer Can Manage the Host's Docker Environment
Once connected, the Portainer dashboard should list the host's existing containers (including the portainer container itself), any images already pulled, existing volumes, and networks. If those counts are all zero or the dashboard is empty, Portainer isn't actually talking to the socket — recheck the -v /var/run/docker.sock:/var/run/docker.sock mount from Step 2.
Confirm write access, not just read access, by deploying a small test Stack. In the left sidebar, go to Stacks → Add stack, give it a name like hello-test, and paste a minimal compose definition. Skip publishing a host port for this test: you just configured careful, restricted access to 9443 in Step 3, and there's no need to open a second port on the host just to prove Portainer can start a container — docker ps is enough evidence.
yaml
version: "3"
services:
web:
image: nginx:latest
“General note for any stack you deploy later: a docker run -p flag or a compose ports: entry publishes a port through Docker's own iptables rules, which take effect ahead of UFW's rules. A published container port is reachable from the network regardless of your UFW configuration, unless you take an extra step to restrict it (bind to 127.0.0.1, or add rules to the DOCKER-USER iptables chain). Default to binding internal-only services to 127.0.0.1 — for example "127.0.0.1:8080:80" instead of "8080:80" — rather than assuming UFW alone will protect a published port.”
Click Deploy the stack. Portainer pulls the nginx image and starts the container the same way docker compose up would. Confirm it worked from two angles: in Portainer's Containers list, the hello-test-web-1 container should show as running; and from the host's own shell:
bash
docker ps --filter "name=hello-test"
You should see the container listed with a status of Up. Once confirmed, remove the test stack from Portainer's Stacks page (Delete the stack) so it isn't left running unnecessarily.
Troubleshooting
"Unable to connect" or the page timing out when browsing to https://your_server_ip:9443 almost always means the firewall rule from Step 3 isn't actually in place — recheck sudo ufw status verbose and confirm 9443/tcp is listed as ALLOW from the source you're connecting from. If you're reaching Portainer through an Nginx reverse proxy, confirm the proxy's proxy_pass target actually points at port 9443 (or whatever internal port you configured) — a mismatched upstream port is the next most common cause.
If you missed the setup window from Step 4 — the new-admin form stops being accepted or the page errors out partway through — the fix is just to restart the container and reload the page within the fresh window it opens:
bash
docker restart portainer
This is non-destructive: no admin account or settings existed yet, so nothing is lost.
A different, less common failure looks similar but has a different cause and needs a different fix: the setup screen refuses to load at all and specifically claims an admin account already exists, typically because a previous setup attempt partially completed. A restart won't help here — remove the container and its data volume for a clean slate, then rerun Steps 1–2:
“This is destructive only to Portainer's own configuration — its admin accounts, saved settings, and endpoint list. It does not touch, stop, or delete any of the containers, images, volumes, or networks that Portainer was managing on the host; those belong to the Docker daemon, not to Portainer's data volume.”