Dockhand - Une interface Docker pour tout gérer via Git !
Comment j'ai centralisé la gestion de toutes mes stacks Docker avec Dockhand, un outil moderne qui combine l'interface web intuitive, le versioning Git et la gestion sécurisée des secrets.

Le problème : mes stacks sans historique ni centralisation
Quand on gère plusieurs dizaines de services Docker sur plusieurs machines, on se retrouve vite avec un problème : comment garder une trace de tout ?
Avant Dockhand, ma situation ressemblait à ça :
- Des fichiers
compose.yamléparpillés dans/opt/stacks/sur différentes VMs - Aucun historique des modifications (qui a changé quoi ? quand ? pourquoi ?)
- Des secrets en clair dans les fichiers
.env - Impossible de déployer rapidement la même stack sur une autre machine
- Pas de centralisation : je devais SSH sur chaque machine pour modifier une config
Bref, c'était le chaos. Et si je perdais une VM, je perdais aussi tout l'historique de configuration.
La solution : Git comme source of truth + Dockhand pour déployer
La solution est simple et élégante :
- Git devient la source de vérité : tous mes
compose.yamlsont versionnés dans un dépôt Git privé (Forgejo dans mon cas) - Dockhand gère les déploiements : une interface web moderne qui déploie depuis Git et gère les secrets de manière sécurisée
- Fini les secrets en clair : Dockhand chiffre les secrets et les injecte au déploiement
Cette approche me donne :
- Historique complet : chaque modification est tracée dans Git
- Centralisation : un seul endroit pour gérer toutes mes stacks
- Sécurité : les secrets ne sont jamais committés en clair
- Multi-environnements : je peux gérer plusieurs VMs depuis une seule interface
- Reproductibilité : je peux redéployer n'importe quelle stack en quelques clics
Flux GitOps :
Qu'est-ce que Dockhand ?
Dockhand est une interface de gestion Docker moderne développée par Finsys. C'est une alternative légère et élégante à Portainer.
Fonctionnalités principales
- Gestion des conteneurs : démarrer, arrêter, redémarrer, surveiller en temps réel
- Orchestration Compose : éditeur visuel pour les déploiements Docker Compose
- Intégration Git : déploiement depuis des dépôts avec webhooks et synchronisation automatique
- Support multi-environnements : gestion des hôtes Docker locaux et distants
- Terminal et logs : accès shell interactif et streaming de logs en temps réel
- Explorateur de fichiers : navigation, upload et téléchargement depuis les conteneurs
- Gestion des secrets : chiffrement et injection sécurisée des variables sensibles
Stack technologique
- Frontend : SvelteKit 2, Svelte 5, shadcn-svelte, TailwindCSS
- Backend : Runtime Bun avec routes API SvelteKit
- Base : SQLite ou PostgreSQL via Drizzle ORM
- Infrastructure : Communication directe avec l'API Docker
Licence
Dockhand utilise la Business Source License 1.1 (BSL) :
- Gratuit pour : usage personnel, usage interne en entreprise, organisations à but non lucratif, éducation, évaluation
- La licence sera convertie en Apache 2.0 le 1er janvier 2029
Mise en place de Dockhand
Étape 1 : Socket Proxy (sécurité)
Avant de déployer Dockhand, j'utilise un socket proxy pour éviter d'exposer directement le socket Docker aux applications. C'est un principe de moindre privilège : chaque service ne peut accéder qu'aux endpoints Docker dont il a besoin.
Configuration du Socket Proxy (cliquez pour déplier)
services:
socket-proxy:
image: wollomatic/socket-proxy:1.11.0
container_name: socket-proxy
restart: unless-stopped
user: "65534:988" # nobody:docker
mem_limit: 64M
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges
command:
- '-loglevel=info'
- '-listenip=0.0.0.0'
- '-proxycontainername=socket-proxy' # Active les allowlists per-container
- '-watchdoginterval=3600'
- '-stoponwatchdog'
- '-shutdowngracetime=5'
environment:
- SP_ALLOWHEALTHCHECK=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-proxy
healthcheck:
test: ["CMD", "./healthcheck"]
interval: 10s
timeout: 5s
retries: 2
networks:
socket-proxy:
name: socket-proxy
driver: bridge
internal: true
Je détaillerai le socket proxy dans un article dédié. Pour l'instant, retenez que c'est une couche de sécurité entre Docker et vos applications.
Architecture de sécurité :
Ce schéma montre comment chaque couche ajoute une protection supplémentaire entre l'utilisateur et le Docker Engine.
Étape 2 : Déploiement de Dockhand
Voici mon fichier compose.yaml pour Dockhand :
services:
dockhand:
image: fnsys/dockhand:v1.0.14
container_name: dockhand
restart: unless-stopped
ports:
- "192.168.100.160:3001:3000"
networks:
- traefik_private
- socket-proxy
volumes:
- /opt/stacks/dockhand:/opt/stacks/dockhand
environment:
- DATA_DIR=/opt/stacks/dockhand
- TZ=Europe/Paris
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_private"
- "traefik.http.routers.dockhand-local.rule=Host(`dockhand.local.tellserv.fr`)"
- "traefik.http.routers.dockhand-local.entrypoints=local"
- "traefik.http.routers.dockhand-local.tls=true"
- "traefik.http.routers.dockhand-local.tls.certresolver=cloudflare-local"
- "traefik.http.services.dockhand.loadbalancer.server.port=3000"
- "socket-proxy.allow.get=.*"
- "socket-proxy.allow.post=.*"
- "socket-proxy.allow.delete=.*"
- "socket-proxy.allow.head=.*"
networks:
socket-proxy:
external: true
traefik_private:
external: true
Points importants :
- Version de l'image :
v1.0.14- Vérifiez la dernière version stable sur Docker Hub avant de déployer - Port bindé sur IP locale :
192.168.100.160:3001- Adaptez à votre configuration :- Remplacez par votre IP statique locale si vous utilisez Traefik sur la même machine
- Utilisez
127.0.0.1:3001:3000si vous n'utilisez qu'en local sans reverse proxy distant - Évitez
0.0.0.0qui expose le service sur toutes les interfaces (risque de sécurité) - Note : Le port
3001est exposé sur l'hôte, mais Traefik communique en interne sur le port3000du conteneur
- Socket proxy : connexion via le réseau
socket-proxyau lieu d'exposer directement/var/run/docker.sock - Traefik : reverse proxy pour l'accès HTTPS avec certificat automatique
- Permissions socket-proxy : les labels
socket-proxy.allow.*définissent les points de terminaison (endpoints) autorisés de l'API Docker
Dockhand donne un accès complet à votre infrastructure Docker. Ne l'exposez jamais publiquement et utilisez toujours une authentification forte. Gardez ce service en interne uniquement.
Le réseau socket-proxy est déclaré comme external: true, ce qui signifie qu'il doit déjà exister. Assurez-vous de démarrer la stack Socket Proxy AVANT celle de Dockhand, sinon vous obtiendrez une erreur indiquant que le réseau externe est introuvable.
Déploiement :
docker compose up -d
L'interface est maintenant accessible sur https://dockhand.local.tellserv.fr (dans mon cas).
Étape 3 : Ajout des environnements Docker
Une fois Dockhand déployé, je configure les environnements (mes différentes VMs Docker).
Settings → Environments → Add environment

Trois types de connexion sont possibles :
- Unix socket : si vous avez passé directement le socket Docker au conteneur Dockhand (déconseillé)
- Direct connection : connexion HTTP/HTTPS à l'API Docker (via socket proxy dans mon cas)
- Hawser agent (edge) : connexion passive où c'est l'environnement distant qui initie la connexion (parfait pour les machines derrière NAT)
Dans mon cas, j'utilise Direct connection avec mon socket proxy :

- Name :
Tellprod - Connection type :
Direct connection - Host :
socket-proxy(le nom du conteneur) - Port :
2375 - Protocol :
HTTP(le socket proxy n'utilise pas TLS en interne)
Préparation du dépôt Git
Maintenant que Dockhand est opérationnel, je vais migrer toutes mes stacks Docker vers Git.
Étape 1 : Créer le dépôt Git
J'ai créé un dépôt priv é sur mon instance Forgejo : tellprod_compose.git
Étape 2 : Organiser les stacks
Structure de mon dépôt :
tellprod_compose/
├── mobilizon/
│ └── compose.yml
├── audiobookshelf/
│ └── compose.yml
├── freshrss/
│ └── compose.yml
├── zabbix/
│ └── compose.yml
└── ...
Chaque service a son propre dossier avec un fichier compose.yml.
Étape 3 : Supprimer les secrets
CRITIQUE : avant de commit, je dois supprimer tous les secrets de mes fichiers compose.yml et .env.
Les secrets seront gérés par Dockhand et injectés au moment du déploiement.
Par exemple, au lieu de :
environment:
- POSTGRES_PASSWORD=SuperSecretPassword123
- [email protected]
Je mets des variables :
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- MOBILIZON_INSTANCE_EMAIL=${MOBILIZON_INSTANCE_EMAIL}
Étape 4 : Commit et push
git add .
git commit -m "Initial commit: All Compose stacks, with secrets placeholders"
git push origin main

Toutes mes stacks sont maintenant versionnées et centralisées.
Pour éviter tout risque de commit accidentel de secrets, ajoutez un fichier .gitignore à la racine de votre dépôt :
# Fichiers de secrets locaux
.env
*.env
**/.env
**/*.env
# Fichiers temporaires
*.tmp
*.swp
*~
Ainsi, même si vous oubliez de remplacer un secret par une variable, Git refusera de le committer.
