Skip to main content

Portainer best practices


Chapter objectives

  • Secure your Portainer installation
  • Optimize performance
  • Configure high availability
  • Set up monitoring

1 - Security

Secure access

HTTPS mandatory

# Avec certificat Let's Encrypt via Traefik
docker run -d \
--name portainer \
-v portainer_data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce:latest

# Ou avec certificat personnalisé
docker run -d \
-p 9443:9443 \
-v /path/to/certs:/certs \
-v portainer_data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce:latest \
--ssl --sslcert /certs/cert.pem --sslkey /certs/key.pem

Firewall

# Limiter l'accès au port Portainer
ufw allow from 192.168.1.0/24 to any port 9443
ufw allow from 10.0.0.0/8 to any port 9001 # Agent

Protecting the Docker socket

┌─────────────────────────────────────────────────────────────────┐
│ Options de sécurité socket │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Option 1: Socket direct (développement) │
│ -v /var/run/docker.sock:/var/run/docker.sock │
│ ⚠️ Accès root complet au daemon Docker │
│ │
│ Option 2: Agent Portainer (recommandé production) │
│ Agent sur chaque hôte, socket isolé │
│ ✅ Meilleure isolation │
│ │
│ Option 3: TLS mutual auth │
│ Socket exposé via TCP avec certificats │
│ ✅ Authentification forte │
│ │
└─────────────────────────────────────────────────────────────────┘

Password policy

CriterionRecommendation
LengthMinimum 12 characters
ComplexityUppercase, lowercase, numbers, symbols
ExpirationRotation every 90 days
HistoryProhibit the last 5 passwords

2 - Production configuration

Environment variables

docker run -d \
--name portainer \
-p 9443:9443 \
-e HIDE_CONFIGURATION_FIELDS=enable \
-e NO_ANALYTICS=1 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
VariableDescription
NO_ANALYTICSDisable telemetry
HIDE_CONFIGURATION_FIELDSHide passwords
ADMIN_PASSWORDSet the admin password at startup

Resource limits

# docker-compose.yml
services:
portainer:
image: portainer/portainer-ce:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
restart: unless-stopped

3 - High availability

HA architecture

┌─────────────────────────────────────────────────────────────────┐
│ High Availability Setup │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Load Balancer │ │
│ │ (HAProxy/ │ │
│ │ Traefik) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Portainer │ │ Portainer │ │ Portainer │ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ Shared Volume │ │
│ │ (NFS/EFS) │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Swarm HA deployment

# portainer-ha.yml
version: "3.9"

services:
portainer:
image: portainer/portainer-ce:latest
command: -H tcp://tasks.agent:9001 --tlsskipverify
volumes:
- portainer_data:/data
networks:
- agent_network
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
update_config:
parallelism: 1
delay: 10s
failure_action: rollback

agent:
image: portainer/agent:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- agent_network
deploy:
mode: global

networks:
agent_network:
driver: overlay
attachable: true

volumes:
portainer_data:
driver: local
driver_opts:
type: nfs
o: addr=nfs-server,rw
device: ":/portainer"

4 - Backup and restore

Back up the data

# Arrêter Portainer
docker stop portainer

# Sauvegarder le volume
docker run --rm \
-v portainer_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/portainer-backup-$(date +%Y%m%d).tar.gz /data

# Redémarrer Portainer
docker start portainer

Automatic backup script

#!/bin/bash
# backup-portainer.sh

BACKUP_DIR=/backups/portainer
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)

# Créer le backup
docker run --rm \
-v portainer_data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/portainer-$DATE.tar.gz /data

# Nettoyer les anciens backups
find $BACKUP_DIR -name "portainer-*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: portainer-$DATE.tar.gz"

Restore

# Arrêter Portainer
docker stop portainer

# Supprimer les anciennes données
docker run --rm -v portainer_data:/data alpine rm -rf /data/*

# Restaurer
docker run --rm \
-v portainer_data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/portainer-backup.tar.gz -C /

# Redémarrer
docker start portainer

5 - Monitoring Portainer

Healthcheck

services:
portainer:
image: portainer/portainer-ce:latest
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9000/api/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

Prometheus metrics

Portainer exposes metrics on /api/endpoints/{id}/docker/info:

# prometheus.yml
scrape_configs:
- job_name: 'portainer'
static_configs:
- targets: ['portainer:9000']
metrics_path: /api/system/status
bearer_token: 'your-api-token'

Grafana dashboard

┌─────────────────────────────────────────────────────────────────┐
│ Portainer Monitoring Dashboard │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Environments │ │ Containers │ │ Stacks │ │
│ │ 3 │ │ 47 │ │ 12 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Portainer Uptime │ │
│ │ ████████████████████████████████████████████ 99.9% │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ API Response Time │ │
│ │ ▁▂▃▂▁▂▃▄▃▂▁▂▃▂▁ avg: 45ms │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

6 - API and automation

Generate an API token

  1. Settings > API > Add access token
  2. Name the token
  3. Copy and store it securely

Using the API

# Obtenir la liste des environnements
curl -s -H "X-API-Key: ptr_xxxx" \
https://portainer.example.com/api/endpoints

# Lister les conteneurs
curl -s -H "X-API-Key: ptr_xxxx" \
https://portainer.example.com/api/endpoints/1/docker/containers/json

# Démarrer un conteneur
curl -X POST -H "X-API-Key: ptr_xxxx" \
https://portainer.example.com/api/endpoints/1/docker/containers/abc123/start

Automation script

#!/usr/bin/env python3
import requests

PORTAINER_URL = "https://portainer.example.com"
API_KEY = "ptr_xxxxxxxxxxxx"

headers = {"X-API-Key": API_KEY}

# Lister les conteneurs arrêtés
response = requests.get(
f"{PORTAINER_URL}/api/endpoints/1/docker/containers/json",
headers=headers,
params={"all": True, "filters": '{"status": ["exited"]}'}
)

stopped = response.json()
print(f"Found {len(stopped)} stopped containers")

# Nettoyer les conteneurs arrêtés depuis plus de 7 jours
for container in stopped:
# ... logique de nettoyage
pass

7 - Production checklist

Before deployment

☐ HTTPS configuré avec certificat valide
☐ Mot de passe admin fort défini
☐ Authentification externe configurée (OAuth/LDAP)
☐ Équipes et permissions définies
☐ Firewall configuré (ports 9443, 9001)
☐ Backups automatisés
☐ Monitoring en place

Configuration

☐ Variables d'environnement de production
☐ Limites de ressources définies
☐ Restart policy configurée
☐ Logs centralisés
☐ Analytics désactivé (NO_ANALYTICS=1)

Security

☐ Socket Docker protégé ou agent utilisé
☐ Edge agents pour environnements distants
☐ Audit logs activé (Business Edition)
☐ Sessions timeout configuré
☐ 2FA activé si disponible

8 - Common troubleshooting

Portainer does not start

# Vérifier les logs
docker logs portainer

# Vérifier les permissions du socket
ls -la /var/run/docker.sock

# Réinitialiser les données (perd la configuration)
docker volume rm portainer_data
docker volume create portainer_data

Connection refused

# Vérifier que le conteneur tourne
docker ps | grep portainer

# Vérifier les ports
docker port portainer

# Tester la connectivité locale
curl -k https://localhost:9443

Reset the password

docker stop portainer

docker run --rm \
-v portainer_data:/data \
portainer/helper-reset-password

docker start portainer

The agent does not connect

# Vérifier que l'agent tourne
docker ps | grep portainer_agent

# Vérifier les logs de l'agent
docker logs portainer_agent

# Vérifier la connectivité réseau
telnet <portainer_ip> 9001

Summary

AspectRecommendation
HTTPSMandatory with a valid certificate
AuthOAuth/LDAP in production
SocketUse the agent rather than the direct socket
BackupDaily with 30-day retention
MonitoringHealthcheck + Prometheus metrics
APITokens for automation
Key points
  • Always secure access with HTTPS
  • Use the agent for production environments
  • Automate backups
  • Set up monitoring
  • Use the API for automation

Practical exercises

  1. Configure HTTPS with a Let's Encrypt certificate
  2. Set up an automatic daily backup
  3. Configure the healthcheck for monitoring
  4. Create an API script to list resources

← Users and RBAC | Exercises and projects →