
Portainer CE: Secure Docker Management UI on Linux
- AuthorKacey H.
- Date
- Time16 minutes
Portainer gives you a clean web UI for Docker without giving every engineer raw SSH access to the daemon. Used carefully, it speeds up stack deployments and troubleshooting. Used carelessly, it becomes a privileged attack surface. This guide installs Portainer CE the right way: HTTPS only, strong admin setup, and least-privilege team access.
Prerequisites
- Ubuntu 22.04+ or Debian 12 with Docker Engine and Compose plugin installed
- Domain for portainer.example.com
- Nginx or Caddy available for TLS termination
- Firewall allowing 80/443 only from trusted networks when possible
Step 1: Create Volumes and Compose File
sudo mkdir -p /opt/portainer
cd /opt/portainer
sudo tee docker-compose.yml >/dev/null <<'EOF'
services:
portainer:
image: portainer/portainer-ce:2.21.4
container_name: portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9443:9443"
volumes:
portainer_data:
EOF
sudo docker compose up -d
sudo docker compose psStep 2: Put Portainer Behind Nginx TLS
sudo tee /etc/nginx/sites-available/portainer >/dev/null <<'EOF'
server {
listen 80;
server_name portainer.example.com;
location / {
proxy_pass https://127.0.0.1:9443;
proxy_ssl_verify off;
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/portainer /etc/nginx/sites-enabled/portainer
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d portainer.example.com --redirectStep 3: Complete First-Time Admin Setup
Visit https://portainer.example.com within five minutes of first start and create the admin user. If the window expires, restart the container and try again. Use a long password and enable MFA in Settings → Authentication once the environment is online.
Step 4: Deploy a Sample Stack
# In Portainer UI: Stacks → Add stack → paste compose
# Example: whoami behind the same host network pattern
services:
whoami:
image: traefik/whoami:v1.10
restart: unless-stopped
ports:
- "127.0.0.1:8081:80"Step 5: Restrict Access and Roles
- Create non-admin users for day-to-day operators.
- Scope environment access per team instead of sharing the admin account.
- Prefer deploying via Git-backed stacks when possible to keep audit trails.
- Lock down source IPs with firewall or VPN if Portainer manages production hosts.
Step 6: Backup Portainer Data
sudo docker run --rm \\
-v portainer_portainer_data:/data \\
-v /var/backups:/backup \\
alpine tar czf /backup/portainer-$(date +%F).tar.gz -C /data .
# Restore
sudo docker compose -f /opt/portainer/docker-compose.yml down
sudo docker run --rm \\
-v portainer_portainer_data:/data \\
-v /var/backups:/backup \\
alpine sh -c 'rm -rf /data/* && tar xzf /backup/portainer-2026-08-03.tar.gz -C /data'
sudo docker compose -f /opt/portainer/docker-compose.yml up -dProduction Checklist
- Never publish Portainer ports on 0.0.0.0 without TLS and access control.
- Keep Docker socket access limited to Portainer and trusted automation only.
- Pin image tags instead of using latest.
- Review users and API tokens quarterly.
- Document which stacks are managed in Portainer versus GitOps tooling.
"A container UI is an operations accelerator only when authentication, TLS, and change ownership are treated as production controls."



