Virtualization

Manage Docker Containers with Portainer on a VPS

Once you start running more than a handful of Docker containers on a VPS, the terminal dashboard gets cluttered. You type docker ps several times a day, attach to logs with docker logs -f, and occasionally delete a dangling image. It works, but it scales poorly when you manage multiple services or need to give a non-technical team member visibility into the stack. This is where Portainer fits.

使用 Portainer 管理 Docker 容器时,无需反复输入命令,通过 Web 界面即可进行日常维护。

Managing Docker containers with Portainer lets you skip repetitive shell commands, the web dashboard handles routine maintenance.

Prerequisites

  • A Linux VPS (this guide uses Ubuntu 24.04 LTS). The same steps work on Debian 12, AlmaLinux 9, or Rocky Linux 9 with package-manager adjustments.
  • Docker and Docker Compose v2 already installed. If Docker is not yet on your VPS, run apt install docker.io docker-compose-v2 (Ubuntu/Debian) or dnf install docker docker-compose-plugin (AlmaLinux/Rocky), then systemctl enable --now docker.
  • A non-root user with sudo privileges.
  • A firewall that passes port 9443 (the Portainer web UI) or 8000 (for the Edge agent, if needed). In production, restrict source IPs or put Portainer behind a reverse proxy with authentication.

Why Use a Container Management UI?

Portainer does not replace Docker, it wraps it. Behind the scenes every click maps to a Docker API call. What changes is the workflow: instead of composing a docker run invocation from memory, you browse a list of running containers, restart one, inspect its logs, and adjust its environment variables, all from a browser. The built-in App Templates let you deploy stacks (WordPress, Nginx, MySQL, n8n) in a few clicks, which speeds up prototyping on a fresh VPS.

The main risk with a UI is that it can encourage click-to-deploy chaos, containers that are never documented, ports that overlap, orphaned volumes. Portainer itself enforces very little: you still need a mental model of what each container does. The benefit is that one web dashboard replaces a dozen terminal windows for routine ops, especially when you manage several VPS nodes or collaborate with a team.

Step 1, Deploy the Portainer Server Container

Portainer runs as a Docker container. The Business Edition (BE) has extra features like RBAC and registry management, but the Community Edition (CE) is enough for single-server use. We deploy CE on the local Docker socket.

docker volume create portainer_data

docker run -d -p 8000:8000 -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce

Breaking this down:

  • -p 8000:8000, the Edge agent port, optional unless you plan to connect remote agents later.
  • -p 9443:9443, the web UI, served over HTTPS (Portainer uses a self-signed cert by default).
  • -v /var/run/docker.sock, gives Portainer full control over the Docker engine on this host.
  • portainer_data volume, persists the database, so restarts don't lose your configurations.

Verify the container is running:

docker ps --filter name=portainer

Expected output contains portainer/portainer-ce with status Up.

Step 2, Initial Web Setup

Open https://<your-vps-ip>:9443 in a browser. Your browser will warn about the self‑signed certificate, that is normal for a first setup; proceed past it.

  1. Create the admin user: set a username (default admin) and a strong password (at least 12 characters, mixed).
  2. Select the environment: Portainer asks you to connect an environment (a Docker host, Swarm cluster, or Kubernetes cluster). Choose Docker Standalone and then Socket, this connects to the local engine via the bind-mounted socket from Step 1.
  3. Name the environment: something descriptive like primary-vps. Click Connect.

After a few seconds you land on the Portainer dashboard, which shows the number of containers, images, volumes, and networks on this host. The Quick Actions row has buttons for common tasks: inspect logs, stop all, prune unused resources.

Verify: the dashboard should show at least one container (Portainer itself) and the total disk used by Docker.

Step 3, Walk Through the Management Views

Containers

The Containers view lists every running and stopped container. Each row shows the container name, image, status (green dot = running, red = stopped), port mappings, and uptime. Clicking a row reveals options:

  • Logs, tailed in real time, with a search box (much easier than scrolling terminal output).
  • Console, attach an interactive shell (bash, sh, or whatever the container provides).
  • Stats, live CPU and memory graphs, useful for spotting a container that is leaking memory.
  • Actions, start/stop/restart/kill, duplicate, or re-create.

Pro tip: the Inspect tab shows the full JSON configuration for the container (environment variables, bind mounts, health checks). You can copy the JSON and re-create the container on another VPS if needed.

Images

Portainer lists every image pulled to the host. You can Pull a new image by name, Build one from a Dockerfile (paste the Dockerfile content or upload a tar), or Import from a file. The Clean button removes unused (<none>) images, the equivalent of docker image prune -a.

Volumes

The Volumes view shows local volumes (bind mounts are not listed here, only named volumes). You can create, remove, or inspect volumes. Important: if you remove a volume that a container uses, you lose the data, Portainer warns you but does not block the action.

Networks

The default networks (bridge, host, none) are shown. You can create additional custom networks, which is useful when you want several containers to talk over an isolated network (e.g., a database + application stack). This builds a docker network create behind the scenes.

Step 4, Deploy a Container from the UI

Portainer's App Templates ship with about 30 preconfigured stacks (Nginx, MySQL, MariaDB, Redis, WordPress, n8n, etc.). To test the workflow, deploy Nginx:

  1. Go to App Templates in the left-hand menu.
  2. Find or search for Nginx.
  3. Customise the settings (e.g., map host port 8080 to container port 80).
  4. Click Deploy the stack.

Portainer pulls the image, creates the container, and returns to the dashboard. Within a few seconds you should see Nginx running. Hit http://<your-vps-ip>:8080 in a browser to verify the default Nginx page.

For a more realistic deployment (e.g., the thuevps/n8n-vps stack), the Custom Template feature lets you paste a docker-compose.yml directly into Portainer and deploy it as a stack. This is the same as docker compose up -d, but visible and manageable from the UI afterwards.

Portainer CE vs. BE: what changes in daily use on a single VPS
FeatureCE (Community)BE (Business)
User authenticationLocal admin onlyLDAP/OAuth + RBAC
Registry managementManual credential entryBuilt-in credential store
Edge agentBasic (no tunnel)Tunnel-based remote control
Audit logsNot availablePer-action logging
CostFreePer-node license

For a single VPS, CE covers everything you need. BE becomes relevant when you manage 3+ VPS nodes and want centralised user management.

Step 5, Secure the Web UI

The default self-signed certificate works for testing but triggers warnings in every browser. Replace it with a Let's Encrypt certificate, or put Portainer behind a reverse proxy (Nginx, Caddy) that terminates TLS.

Option A, Use Let's Encrypt with Caddy (simpler):

# Stop the existing Portainer container
docker stop portainer && docker rm portainer

# Deploy behind Caddy
docker run -d -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  -e CADDY_OPTIONS="--domain portainer.yourdomain.com" \
  portainer/portainer-ce

Then configure Caddy to proxy portainer.yourdomain.com to localhost:9443. Caddy automatically provisions a Let's Encrypt certificate.

Option B, Restrict access by source IP: Use your firewall to limit port 9443 to known IPs. On a Linux VPS with nftables:

nft add rule inet filter input tcp dport 9443 ip saddr 203.0.113.0/24 accept
nft add rule inet filter input tcp dport 9443 drop

This allows only the IP range 203.0.113.0/24 to reach Portainer, everyone else gets a connection reset.

Troubleshooting

Portainer does not start

Check the container logs: docker logs portainer. Common causes:

  • The Docker socket bind mount path is wrong, ensure /var/run/docker.sock exists.
  • Port 9443 is already in use. Run ss -tlnp | grep 9443 to find the conflicting process, then change Portainer's host port.

Web UI shows "Cannot connect to Docker"

This usually means Portainer lost access to the socket. Verify the container still has the bind mount: docker inspect portainer --format='{{json .Mounts}}' | jq. If the socket volume is missing, recreate the container.

"Container xxx is unhealthy" even though it runs

Portainer relies on the container's health check, if defined. Some images (e.g., portainer/portainer-ce itself) do not ship with a health check. You can add one in the Advanced container settings when you create the container. This is not a Portainer bug.

FAQ

Does Portainer work with Docker Compose stacks?

Yes. Portainer's Stacks view lets you paste a docker-compose.yml and deploy it. It tracks each service as a container in the stack and re-deploys changes when you edit the YAML in the UI.

Can I manage multiple VPS from one Portainer instance?

Yes. In the Business Edition, you add remote Docker hosts or Kubernetes clusters as Edge agents. In the Community Edition, you can add remote endpoints over TCP (the remote engine must expose port 2376 over TLS). This is rarely done on production VPS because exposing the Docker daemon over TCP is a security risk, prefer the Edge agent tunnel or a VPN.

Does Portainer store passwords or API keys securely?

Portainer stores credentials for container registries (Docker Hub, private registries) in its own database, encrypted at rest. The admin password is hashed with bcrypt. The database file lives inside the portainer_data volume, back it up if you care about losing configurations.

How much RAM does Portainer use on a VPS?

On a minimal setup with one connected endpoint, Portainer CE uses roughly 60 to 100 MB of RAM. Peak usage with several stacks being updated can go to 150 MB. It is light enough to run on a 2 GB RAM VPS alongside other containers.

What is the difference between Portainer and Docker Desktop?

Docker Desktop is a developer tool that runs a local Docker daemon inside a VM. Portainer is a management UI that connects to an existing Docker daemon (on a VPS, server, or cluster). On a production VPS, you always want Portainer, never Docker Desktop.

Should I expose Portainer directly to the internet?

No. The web UI is protected by a login screen, but exposing it publicly increases the attack surface. Put it behind a VPN, restrict source IPs in your firewall, or run it behind a reverse proxy that handles authentication (e.g., Caddy with basic auth).

Related articles

使用 Portainer 管理 VPS 上的 Docker 容器

Portainer 是一个轻量级的 Docker Web 管理界面,通过一个容器即可部署在 VPS 上。它不替代 Docker 命令,而是把日常操作(查看日志、重启容器、检查资源占用)转化为可视化点击。安装后占用约 60 to 100 MB 内存,适合 2 GB 以上的 Linux VPS。管理多个容器时尤其省力。安全方面建议用反向代理或防火墙限制访问 IP,不要把 UI 直接暴露在公网上。

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.