Skip to Content

Portainer Docker Setup: Stop Typing `docker ps` and Start Managing Containers Like a Human

Deploy Portainer CE with Docker Compose, connect remote hosts, and manage stacks, volumes, and networks from a clean web UI.

You can memorize every docker flag and subcommand. Or you can install Portainer and manage your entire container fleet from a browser. This Portainer Docker setup guide gets you from zero to a fully operational container management dashboard in 15 minutes—including multi-host support, stack deployment, and the features that actually matter.

Looking for more angles? See our other guides: Portainer Docker Setup: A Developer's Guide to Visual Container Management, Portainer Docker Setup: Manage All Your Containers From One Clean Dashboard, and Portainer Docker Setup: Stop Memorizing CLI Commands and Start Managing Containers Like a Pro.

What You'll Build

By the end of this guide, you'll have:

  • Portainer CE running as a Docker container with persistent data
  • Full visibility into containers, images, volumes, networks, and stacks
  • Remote Docker host management via the Portainer Agent
  • Stack deployment from Git repos and raw Docker Compose files
  • Environment variables and secrets managed through the UI
  • User access control for team-based container management

Prerequisites

Before we start, make sure you have:

  • Docker 20.10+ and Docker Compose v2 installed
  • A Linux server (Ubuntu 22.04/24.04 LTS recommended) or local machine with port 9443 available
  • At least 1GB RAM and 1 CPU core for Portainer itself (your containers are the resource hogs)
  • curl for quick health checks
  • Root or sudo access to create Docker volumes

Portainer is lightweight. It runs happily on a Raspberry Pi or a 1GB VPS alongside your other services. The database and UI state live in a Docker volume, so backups are straightforward.

Step 1: Install Portainer CE with Docker

Portainer consists of two parts: the Server (the web UI) and the Agent (for remote host management). For a single host, we only need the Server. Let's deploy it properly.

1.1 Create the Docker Volume

Portainer stores its database, user accounts, and stack definitions in a Docker volume. Create it first:

docker volume create portainer_data

1.2 Run Portainer Server

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

Port breakdown:

  • 9443 — HTTPS web UI (use this, not HTTP)
  • 8000 — Edge agent tunnel (for remote hosts behind NAT/firewall)
  • /var/run/docker.sock — read-only access to Docker daemon for local container management
  • portainer_data — persistent database and configuration storage

Verify it's running:

docker ps --filter name=portainer
curl -k -s -o /dev/null -w "%{http_code}" https://localhost:9443

You should see a 200 response. Open https://YOUR_SERVER_IP:9443 in a browser. You'll get a self-signed certificate warning—accept it and create your admin account on first login.

Step 2: Docker Compose Deployment (Recommended)

The docker run approach works, but Compose is cleaner for long-term maintenance. Here's the production-ready version:

2.1 Compose File

Create docker-compose.yml:

version: "3.8"

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

volumes:
  portainer_data:

2.2 Deploy and Verify

docker compose up -d
docker compose logs -f portainer

Watch for "Portainer started" in the logs. The first startup takes 10–15 seconds as it initializes the SQLite database.

2.3 Behind a Reverse Proxy (Optional but Recommended)

If you're running Traefik or Nginx, expose Portainer through your reverse proxy instead of directly:

  portainer:
    image: portainer/portainer-ce:2.21.5
    container_name: portainer
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    expose:
      - "9443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - portainer_data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.yourdomain.com`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.services.portainer.loadbalancer.server.port=9443"
      - "traefik.http.services.portainer.loadbalancer.server.scheme=https"
      - "traefik.http.services.portainer.loadbalancer.insecureSkipVerify=true"

networks:
  proxy:
    external: true

This removes the port exposure and serves Portainer over your domain with a proper Let's Encrypt certificate. Note the insecureSkipVerify=true—Portainer uses a self-signed cert internally, and Traefik needs to trust it for the backend connection.

Step 3: Connect and Manage Remote Docker Hosts

Portainer's real power is managing multiple Docker hosts from one dashboard. Here's how to add a remote server.

3.1 Deploy the Portainer Agent

On your remote host, run:

docker run -d \
  -p 9001:9001 \
  --name portainer_agent \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes:ro \
  portainer/agent:2.21.5

The agent exposes port 9001 and reports container, volume, and network state back to the Portainer Server. It does not expose a UI—it's a thin API bridge.

3.2 Add the Environment in Portainer

  1. Log into your Portainer UI
  2. Go to EnvironmentsAdd environment
  3. Select Docker environmentAgent
  4. Enter the remote host's IP and port 9001
  5. Name it (e.g., prod-server-02) and click Connect

Within seconds, you'll see the remote host's containers, images, volumes, and networks in the dashboard. Switch between environments using the selector in the top-left.

3.3 Edge Agent for Hosts Behind NAT

If your remote host is behind a firewall or has no public IP, use the Edge Agent instead. Portainer generates a one-line join command:

docker run -d \
  --name portainer_edge_agent \
  --restart=always \
  -e EDGE=1 \
  -e EDGE_ID=your-edge-id-here \
  -e EDGE_KEY=your-edge-key-here \
  -e CAP_HOST_MANAGEMENT=1 \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes:ro \
  -v /:/host:ro \
  portainer/agent:2.21.5

The Edge Agent initiates an outbound connection to your Portainer Server, tunneling through firewalls without port forwarding. Ideal for edge devices, home servers, and client deployments.

Step 4: Deploy Stacks from Docker Compose

Portainer's stack feature lets you deploy multi-container applications using Docker Compose syntax—directly from the UI or a Git repository.

4.1 Create a Stack Manually

  1. In Portainer, select your environment
  2. Go to StacksAdd stack
  3. Name it whoami-test
  4. Paste this Compose definition:
version: "3.8"

services:
  whoami:
    image: traefik/whoami
    ports:
      - "8080:80"
  1. Click Deploy the stack

Portainer creates the stack, pulls the image, and starts the container. You can view logs, exec into the container, and scale services—all from the UI.

4.2 Deploy from Git (The Production Way)

For real workflows, store your Compose files in Git and let Portainer pull them:

  1. Go to StacksAdd stackRepository
  2. Repository URL: https://github.com/your-org/infrastructure.git
  3. Compose path: stacks/whoami/docker-compose.yml
  4. Authentication: Add a deploy key or personal access token if the repo is private
  5. Enable Automatic updates (webhook or polling) for continuous deployment

This turns Portainer into a lightweight CI/CD endpoint. Push to Git, and Portainer redeploys your stack automatically.

4.3 Environment Variables and Secrets

Portainer supports environment variable injection at stack deployment time. Define them in the UI under Environment variables:

DATABASE_URL=postgres://user:pass@db:5432/app
API_KEY=${API_KEY_SECRET}
DEBUG=false

Reference them in your Compose file with standard ${VAR} syntax. For sensitive values, use Portainer's Configs & Secrets feature to inject files and secrets without exposing them in the UI.

Step 5: Manage Containers, Volumes, and Networks

Portainer exposes the full Docker API through a clean interface. Here's what you can do without touching the CLI.

5.1 Container Operations

  • Start/Stop/Restart/Pause/Kill: One-click actions from the Containers list
  • Logs: Real-time tail with search and download
  • Exec console: Interactive shell inside running containers (no docker exec needed)
  • Stats: CPU, memory, network, and disk I/O graphs
  • Inspect: Full JSON container config, environment variables, and mount points
  • Duplicate/Edit: Clone a container, modify its config, and redeploy

5.2 Volume Management

Navigate to Volumes to see all named and anonymous volumes. You can:

  • Browse volume contents (yes, actually browse files in a Docker volume)
  • Delete unused volumes to reclaim disk space
  • Create new volumes with custom drivers and labels

5.3 Network Management

View bridge, overlay, and custom networks. Inspect connected containers, create new networks with custom subnets, and attach containers to multiple networks—all through the UI.

5.4 Image Management

Pull new images, build from Dockerfiles, tag, push to registries, and prune dangling images. The Unused images filter helps you clean up after aggressive development cycles.

Step 6: Tips, Troubleshooting, and Production Hardening

6.1 Portainer Won't Start

If Portainer fails to initialize, check volume permissions:

docker logs portainer
ls -la /var/lib/docker/volumes/portainer_data/_data/

Common causes:

  • Port 9443 already in use: Change the host port mapping or stop the conflicting service
  • Docker socket not accessible: Ensure /var/run/docker.sock exists and Portainer has read-only access
  • Corrupted database: The SQLite DB can get wedged on unclean shutdown. Restore from backup or reinitialize

6.2 Agent Connection Fails

If the Portainer Server can't reach a remote agent:

  • Verify port 9001 is open on the remote host firewall
  • Check that the agent container is running: docker ps --filter name=portainer_agent
  • Test connectivity: curl http://REMOTE_IP:9001/ping (should return {"ping":"OK"})
  • For Edge Agents, verify the EDGE_ID and EDGE_KEY match what's shown in the Portainer UI

6.3 Stack Deployment Errors

Portainer validates Compose syntax before deploying. Common failures:

  • Named volumes not created: Portainer creates them automatically, but external volumes must exist first
  • Port conflicts: Two stacks can't bind to the same host port. Use dynamic ports or a reverse proxy
  • Environment variables missing: Portainer highlights unset variables before deployment

6.4 Backup and Restore

Your Portainer database lives in the portainer_data volume. Back it up regularly:

docker run --rm \
  -v portainer_data:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/portainer-backup-$(date +%F).tar.gz -C /data .

Restore:

docker run --rm \
  -v portainer_data:/data \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/portainer-backup-2026-07-05.tar.gz"

6.5 Security Hardening

  • Change the default admin password immediately on first login
  • Enable LDAP or OAuth for team access: Settings → Authentication
  • Restrict access to Portainer's port—don't expose 9443 to the internet without a reverse proxy and firewall rules
  • Use RBAC (Business Edition feature) to limit users to specific environments and stacks
  • Enable audit logging to track who deployed what and when

Wrapping Up

You now have a fully operational Portainer instance managing local and remote Docker hosts, deploying stacks from Git, and giving you full visibility into containers, volumes, networks, and images—without memorizing a single docker flag.

Portainer bridges the gap between Docker's raw power and the usability most teams actually need. For solo developers, it eliminates CLI fatigue. For teams, it provides shared visibility, access control, and deployment consistency.

That said, production container orchestration at scale—multi-node Swarm clusters, Kubernetes integration, GitOps pipelines, and custom registry management—requires careful architecture.

Need help designing a production container management strategy, integrating Portainer into your CI/CD pipeline, or migrating from manual Docker workflows to managed stacks? Get in touch with our team—we've deployed Portainer-backed infrastructure for SaaS platforms, IoT fleets, and enterprise DevOps teams. We'll get your containers under control.

Traefik Reverse Proxy Guide: Route HTTPS Traffic to Docker Services Without Losing Your Mind
Set up Traefik with Docker Compose, auto-SSL via Let's Encrypt, and intelligent routing—so your services just work.