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
| Criterion | Recommendation |
|---|---|
| Length | Minimum 12 characters |
| Complexity | Uppercase, lowercase, numbers, symbols |
| Expiration | Rotation every 90 days |
| History | Prohibit 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
| Variable | Description |
|---|---|
NO_ANALYTICS | Disable telemetry |
HIDE_CONFIGURATION_FIELDS | Hide passwords |
ADMIN_PASSWORD | Set 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
- Settings > API > Add access token
- Name the token
- 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
| Aspect | Recommendation |
|---|---|
| HTTPS | Mandatory with a valid certificate |
| Auth | OAuth/LDAP in production |
| Socket | Use the agent rather than the direct socket |
| Backup | Daily with 30-day retention |
| Monitoring | Healthcheck + Prometheus metrics |
| API | Tokens 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
- Configure HTTPS with a Let's Encrypt certificate
- Set up an automatic daily backup
- Configure the healthcheck for monitoring
- Create an API script to list resources