Docker transformed how we deploy applications, but managing containers entirely from the command line can feel like navigating a cockpit with no instruments. You know the containers are running — somewhere — but checking logs means remembering syntax, restarting a service means finding the right container ID, and understanding resource usage means juggling multiple terminal windows. Portainer solves this by giving you a full-featured web dashboard for managing Docker environments — containers, images, volumes, networks, and even multi-host deployments — all from your browser.

Whether you're a developer who prefers visual interfaces, a team lead who needs to give colleagues container access without teaching them CLI, or a solo operator managing several services on a VPS, Portainer is one of the most useful tools you can install. It runs as a single lightweight container, adds virtually no overhead, and can be deployed in under two minutes.

MassiveGRID Ubuntu VPS includes: Ubuntu 24.04 LTS pre-installed · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · Independent CPU/RAM/storage scaling · 12 Tbps DDoS protection · 4 global datacenter locations · 100% uptime SLA · 24/7 human support rated 9.5/10

Deploy a self-managed VPS — from $1.99/mo
Need dedicated resources? — from $19.80/mo
Want fully managed hosting? — we handle everything

What Portainer Does (And Who It's For)

Portainer is an open-source container management platform that provides a web-based GUI for Docker, Docker Swarm, and Kubernetes environments. The Community Edition (CE) is free and covers everything most self-hosters and small teams need.

With Portainer, you can:

Portainer is particularly valuable for these use cases:

Prerequisites

Before installing Portainer, you need:

Verify Docker is running:

docker --version
docker compose version
sudo systemctl status docker

You should see Docker Engine 24.x+ and Docker Compose v2.x+ with the Docker service active and running.

Method 1: Deploy Portainer with Docker CLI (Quick Install)

The fastest way to get Portainer running is a single Docker command. First, create a volume to persist Portainer's data (user accounts, settings, stack definitions):

sudo docker volume create portainer_data

Now deploy the Portainer CE container:

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

Let's break down each flag:

Verify the container is running:

sudo docker ps --filter name=portainer

You should see the container listed with status "Up." Portainer is now accessible at https://your-server-ip:9443.

Note: Portainer uses a self-signed SSL certificate by default on port 9443. Your browser will show a security warning — this is expected. We'll fix this by adding a proper SSL certificate via Nginx reverse proxy later in this guide.

Method 2: Deploy Portainer with Docker Compose (Recommended)

For production use, Docker Compose gives you a version-controlled, reproducible deployment. Create a directory for the Portainer stack:

sudo mkdir -p /opt/portainer
sudo nano /opt/portainer/docker-compose.yml

Add the following configuration:

services:
  portainer:
    image: portainer/portainer-ce:2.21-latest
    container_name: portainer
    restart: always
    security_opt:
      - no-new-privileges:true
    ports:
      - "8000:8000"
      - "9443:9443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    environment:
      - TZ=UTC

volumes:
  portainer_data:
    driver: local

Deploy the stack:

cd /opt/portainer
sudo docker compose up -d

Check the logs to confirm successful startup:

sudo docker compose logs -f portainer

You should see output indicating the server has started successfully on port 9443. Press Ctrl+C to exit the log stream.

Securing Portainer Behind Nginx Reverse Proxy with SSL

Accessing Portainer directly on port 9443 with a self-signed certificate isn't ideal for production. A much better approach is to put Portainer behind Nginx with a Let's Encrypt SSL certificate. If you don't have Nginx installed yet, see our Nginx reverse proxy guide.

First, modify the Docker Compose file to only expose Portainer on localhost. Update /opt/portainer/docker-compose.yml:

services:
  portainer:
    image: portainer/portainer-ce:2.21-latest
    container_name: portainer
    restart: always
    security_opt:
      - no-new-privileges:true
    ports:
      - "127.0.0.1:9443:9443"
      - "8000:8000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    environment:
      - TZ=UTC

volumes:
  portainer_data:
    driver: local

Recreate the container with the updated port binding:

cd /opt/portainer
sudo docker compose up -d

Now create the Nginx virtual host. Create a new configuration file:

sudo nano /etc/nginx/sites-available/portainer.example.com

Add the following configuration (replace portainer.example.com with your actual domain):

server {
    listen 80;
    server_name portainer.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name portainer.example.com;

    ssl_certificate /etc/letsencrypt/live/portainer.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/portainer.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    client_max_body_size 100M;

    location / {
        proxy_pass https://127.0.0.1:9443;
        proxy_ssl_verify off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_buffering off;
    }
}

Note the proxy_pass https:// — Portainer's internal server uses HTTPS even locally, and proxy_ssl_verify off tells Nginx to accept the self-signed certificate for the backend connection. The WebSocket headers (Upgrade and Connection) are important because Portainer uses WebSockets for the container console feature.

Enable the site and obtain an SSL certificate using Let's Encrypt (see our Let's Encrypt guide for detailed instructions):

sudo ln -s /etc/nginx/sites-available/portainer.example.com /etc/nginx/sites-enabled/
sudo certbot --nginx -d portainer.example.com
sudo nginx -t
sudo systemctl reload nginx

Now access Portainer at https://portainer.example.com with a valid SSL certificate.

Creating Your Admin Account and Securing Access

When you first access Portainer, you'll see a setup wizard. This is time-sensitive — Portainer requires you to create an admin account within 5 minutes of the first startup, or it will lock itself for security.

If you missed the window:

cd /opt/portainer
sudo docker compose restart portainer

On the setup screen:

  1. Enter a username (default is admin, but consider something less predictable)
  2. Set a strong password (minimum 12 characters required)
  3. Click "Create user"

After creating the admin account, you'll be asked to connect Portainer to your Docker environment. Select "Get Started" to automatically connect to the local Docker instance (the one whose socket you mounted). You should immediately see your local environment listed with the Portainer container running.

Disabling New User Registration

By default, Portainer does not allow open registration after the initial admin is created. But verify this explicitly: navigate to Settings → Authentication and confirm that the authentication method is set to "Internal" and that no public sign-up option is enabled.

Adding Team Members

If colleagues need access, create accounts manually: go to UsersAdd user. Portainer CE supports two roles:

For team environments, create individual user accounts and assign them to specific environments rather than sharing the admin credentials.

Deploying Your First Stack from a Template

Portainer's "App Templates" feature lets you deploy popular applications with a few clicks. Navigate to App Templates in the sidebar. You'll find templates for common services like Nginx, MySQL, PostgreSQL, Redis, WordPress, and more.

To deploy a template:

  1. Click on the application (e.g., "Nginx")
  2. Give the container a name
  3. Optionally configure network and volume settings
  4. Click "Deploy the container"

While templates are convenient for quick experiments, for production workloads, you'll want to use custom stacks with Docker Compose files — which brings us to the most powerful feature of Portainer.

Managing Docker Compose Stacks Through the GUI

Stacks are Portainer's term for Docker Compose deployments, and this is where Portainer truly shines. Instead of SSH-ing into your server and editing YAML files, you can create, edit, and manage entire multi-container applications from the web interface.

Creating a New Stack

Navigate to StacksAdd stack. You have several input methods:

Let's create a practical example — a full WordPress stack with MySQL. In the web editor, enter:

services:
  wordpress:
    image: wordpress:6.7-php8.3-apache
    container_name: wordpress_app
    restart: always
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: wordpress_db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html
    depends_on:
      - wordpress_db

  wordpress_db:
    image: mysql:8.0
    container_name: wordpress_db
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

volumes:
  wordpress_data:
  db_data:

Below the editor, you'll see an "Environment variables" section. Add:

This is one of Portainer's best features — environment variables are stored securely and separately from the Compose file, so you can share stack definitions without exposing secrets.

Click "Deploy the stack" and Portainer will pull the images and start the containers. You can watch the deployment progress in real time.

Editing an Existing Stack

To modify a running stack, go to Stacks, click on the stack name, and select the "Editor" tab. Make your changes to the YAML, then click "Update the stack." Portainer will intelligently recreate only the containers that changed — the same behavior as running docker compose up -d after editing a file.

Git-Based Stacks (GitOps Workflow)

For production environments, the Git repository option is powerful. Point Portainer to a Git repo containing your docker-compose.yml, and it can:

This creates a simple GitOps workflow: push a change to your repo, and Portainer detects and deploys it automatically.

Container Logs, Stats, and Console Access

These three features alone justify installing Portainer for many users.

Container Logs

Click on any container, then click "Logs." You get a real-time log viewer with:

This replaces docker logs -f --tail 500 container_name with something far more usable — especially when you need to search through output or share log excerpts with a team member.

Container Stats

The "Stats" view shows real-time graphs for:

This is the equivalent of running docker stats in a terminal, but presented as visual graphs that update every few seconds. It's invaluable for spotting memory leaks, CPU spikes, or unexpected network activity.

Console Access

Click "Console" on any container to open a web-based terminal session inside the container. You can choose between /bin/bash, /bin/sh, or a custom command. This replaces docker exec -it container_name /bin/bash and works entirely in the browser — no SSH needed.

This is particularly useful for debugging: you can quickly check file contents, test network connectivity from inside a container, or inspect the running process.

Portainer Edge Agents for Multi-Server Management

If you manage Docker environments across multiple servers — for example, a production server, a staging server, and a development server — Portainer's Edge Agent feature lets you manage all of them from a single Portainer instance.

The architecture works like this:

Edge Agents initiate an outbound connection to the Portainer Server, meaning remote servers don't need any inbound ports opened for management — the agent connects out through port 8000.

Setting Up an Edge Agent

In your Portainer dashboard, go to EnvironmentsAdd environmentEdge Agent. Portainer will generate a deployment command specific to your instance. It looks like this:

docker run -d \
  --name portainer_edge_agent \
  --restart always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes \
  -v /:/host \
  -v portainer_agent_data:/data \
  -e EDGE=1 \
  -e EDGE_ID=your-unique-edge-id \
  -e EDGE_KEY=your-edge-key \
  -e EDGE_INSECURE_POLL=1 \
  -e PORTAINER_HOST=https://portainer.example.com \
  -e PORTAINER_PORT=443 \
  portainer/agent:2.21-latest

Run this command on each remote server you want to manage. Within a minute, the environment will appear in your Portainer dashboard, and you can manage it exactly like your local Docker environment — stacks, containers, logs, console, everything.

Performance note: Portainer adds virtually no overhead — the server runs in under 100MB RAM alongside your application containers. Edge agents use even less. Deploy a Cloud VPS to run Portainer as your central management hub.

Managing Images and Cleaning Up

Over time, Docker accumulates unused images, stopped containers, and orphaned volumes. Portainer makes cleanup visible and manageable.

Navigate to Images to see every Docker image on your server, including:

You can select unused images and remove them in bulk. For a more aggressive cleanup, the "Remove all unused images" button is equivalent to running:

docker image prune -a

Similarly, the Volumes page shows all Docker volumes with their sizes and which containers reference them. Orphaned volumes (not attached to any container) can be identified and removed. For managing overall disk space on your VPS, see our disk space management guide.

Portainer vs Coolify vs Dokploy — When to Use What

Portainer is not the only web-based Docker management tool. Two popular alternatives are Coolify and Dokploy, both of which overlap with Portainer but serve different primary use cases.

Feature Portainer CE Coolify Dokploy
Primary purpose Docker environment management Self-hosted PaaS (Heroku alternative) Self-hosted PaaS (deployment platform)
Git push-to-deploy Limited (Git-based stacks) Yes (core feature) Yes (core feature)
Automatic SSL No (manual/external) Yes (built-in via Traefik) Yes (built-in via Traefik)
Database management Deploy via containers One-click databases One-click databases
Docker Compose support Full (native) Yes Yes
Container-level control Full (logs, stats, console, exec) Basic Basic
Multi-host management Yes (Edge Agents) Yes (remote servers) Yes (multi-server)
Kubernetes support Yes No No
Resource overhead ~50-100MB RAM ~300-500MB RAM ~200-400MB RAM
Best for Managing Docker infrastructure Deploying web applications Deploying web applications

Choose Portainer when:

Choose Coolify or Dokploy when:

Portainer and Coolify/Dokploy are not mutually exclusive. Some operators run Portainer alongside Coolify to get the best of both worlds — Coolify for application deployment, Portainer for infrastructure visibility.

Updating Portainer

Portainer is updated by pulling the latest image and recreating the container. Your data persists in the Docker volume.

If you used Docker Compose:

cd /opt/portainer
sudo docker compose pull
sudo docker compose up -d

If you used the single Docker run command:

sudo docker stop portainer
sudo docker rm portainer
sudo docker pull portainer/portainer-ce:2.21-latest
sudo docker run -d \
  --name portainer \
  --restart=always \
  -p 8000:8000 \
  -p 127.0.0.1:9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:2.21-latest

Your admin account, stack definitions, environment configurations, and all settings are preserved in the portainer_data volume.

Backing Up Portainer

Portainer stores its entire configuration in the Docker volume. To back it up:

# Stop Portainer temporarily
cd /opt/portainer
sudo docker compose stop portainer

# Back up the volume
sudo docker run --rm \
  -v portainer_data:/data \
  -v /opt/backups:/backup \
  ubuntu:24.04 \
  tar czf /backup/portainer-backup-$(date +%Y%m%d).tar.gz -C /data .

# Restart Portainer
sudo docker compose start portainer

To restore from a backup:

# Stop Portainer
sudo docker compose stop portainer

# Clear existing data and restore
sudo docker run --rm \
  -v portainer_data:/data \
  -v /opt/backups:/backup \
  ubuntu:24.04 \
  bash -c "rm -rf /data/* && tar xzf /backup/portainer-backup-20260228.tar.gz -C /data"

# Start Portainer
sudo docker compose start portainer

For automated backup strategies, see our automatic backups guide.

Security Best Practices

Portainer has access to the Docker socket, which effectively means root-level access to your server. Take these precautions:

1. Always Use HTTPS

Never expose Portainer over plain HTTP. The Nginx reverse proxy with SSL approach described above is the minimum. If you access Portainer over HTTP, your admin credentials travel in plain text.

2. Restrict Access by IP (Optional)

If only specific IP addresses need Portainer access, add IP restriction to the Nginx configuration:

location / {
    allow 203.0.113.10;    # Your office IP
    allow 198.51.100.20;   # Your home IP
    deny all;

    proxy_pass https://127.0.0.1:9443;
    # ... rest of proxy config
}

3. Use Strong Passwords and Consider 2FA

Portainer Business Edition supports OAuth and LDAP. For CE, use a strong, unique password for the admin account. You can add an extra authentication layer at the Nginx level with HTTP basic auth as a second factor.

4. Keep Portainer Updated

Portainer handles Docker socket communication — security patches matter. Check for updates monthly and apply them promptly.

For comprehensive VPS security practices, review our security hardening guide.

Troubleshooting Common Issues

Portainer Shows "Waiting for Edge Agent" for Local Environment

This usually means the Docker socket mount is incorrect. Verify:

sudo ls -la /var/run/docker.sock

The socket should exist and be owned by the docker group. If using rootless Docker, the socket path will be different.

Cannot Connect to Portainer After Reboot

Check that both Docker and the Portainer container started:

sudo systemctl status docker
sudo docker ps -a --filter name=portainer

If the container is in "Exited" state, start it manually and check logs:

sudo docker start portainer
sudo docker logs portainer

WebSocket Errors in Container Console

If the in-browser console doesn't connect, the Nginx configuration is likely missing the WebSocket upgrade headers. Ensure these lines are in your Nginx location block:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Stacks Show as "Limited" After Portainer Restart

This happens when stacks were originally deployed outside of Portainer (via CLI docker compose up). Portainer can detect these running services but cannot fully manage them. The solution is to redeploy through Portainer's stack interface — copy the Compose file content, create a new stack, and deploy.

Prefer Managed Container Hosting?

Portainer gives you powerful visual management for self-hosted Docker environments. But if you'd rather not manage the underlying infrastructure at all — operating system updates, security patches, Docker daemon maintenance, backup automation — MassiveGRID's fully managed dedicated cloud servers handle the entire stack for you. Your containers run on dedicated resources with high-availability failover, and the MassiveGRID team manages everything underneath.

Scaling up? Managing 10+ containers across multiple stacks benefits from dedicated resources that ensure predictable performance. MassiveGRID Dedicated VPS plans start at $19.80/mo with guaranteed CPU, RAM, and NVMe storage that never competes with other tenants.

Summary

Portainer transforms Docker management from a purely command-line experience into something accessible, visual, and team-friendly. To recap the key steps:

  1. Deploy Portainer CE as a single Docker container — it takes under 2 minutes
  2. Secure it behind Nginx with a proper SSL certificate
  3. Create your admin account immediately (5-minute window)
  4. Use Stacks to manage Docker Compose deployments through the browser
  5. Leverage container logs, stats, and console for day-to-day operations
  6. Add Edge Agents for multi-server management from a single dashboard

For monitoring your Portainer-managed infrastructure, consider setting up server monitoring alongside the container-level visibility Portainer provides. And if you're using Traefik instead of Nginx as your reverse proxy, our Traefik guide covers integrating it with Docker containers like Portainer.