đ Jump to đŹđ§ Englishâ · Aller au đ«đ· Françaisâ
A self-hosted, containerized scheduler that safely tests, pulls, rebuilds and restarts your other Docker Compose projects â with a built-in scheduler, no host cron, no direct Docker socket exposure, and multi-channel notifications (email, Discord, ntfy, Telegram, Gotify, Pushover).
Point it at a folder full of ~/projects/<name>/docker-compose.yml projects
and it takes care of the rest: run each project's tests, compare image
digests before/after pull, rebuild local Dockerfiles, restart only what
changed, verify containers are still up 10 seconds later, clean up unused
images, and notify you â only when something actually happened.
find -L, symlink-aware) for any
docker-compose.yml, at a configurable depth.test.sh or make test (if a Makefile defines a
test: target). A failing test cancels the update for that project only.docker compose pull â no restart if nothing changed.Dockerfile, it's
rebuilt every run.latest.log symlink, with automatic
retention/cleanup.UPDATE_TIME), no dependency
on the host's cron.PUID/PGID), never touches
/var/run/docker.sock directly â it goes through a filtering
Tecnativa docker-socket-proxyâ ..env â nothing is
hardcoded.âââââââââââââââââââââââââââââ tcp://âŠ:2375 âââââââââââââââââââââââââââââ
â docker-update â âââââââââââââââââââș â docker-socket-proxy â
â (scheduler + update logic)â â (filters the Docker API) â
â non-root user â â â
âââââââââââââââââââââââââââââ ââââââââââââââŹââââââââââââââ
â ro bind-mount
âŒ
/var/run/docker.sock
(host Docker daemon)
docker-update never runs its own Docker daemon. It sends docker compose
commands over the network to docker-socket-proxy, which is the only
container allowed to touch the real socket â and only for the operations it
explicitly allows (see Securityâ ).
â ïž Important â this is "Docker-outside-of-Docker": the commands run inside
docker-update, but they're executed by the host's Docker daemon. When your project'sdocker-compose.ymlresolves a relative bind mount or build context, it's the host that interprets that path â not thedocker-updatecontainer. That's whyPROJETS_DIRmust be mounted at the exact same absolute path on the host and inside the container (docker-compose.ymldoes this:${PROJETS_DIR}:${PROJETS_DIR}). If the paths differ, your other projects' volumes will resolve incorrectly.
/home/you/projects/
âââ nextcloud/
â âââ docker-compose.yml
â âââ test.sh # optional â exit 0 = pass, anything else = cancel update
âââ vaultwarden/
â âââ docker-compose.yml
â âââ Dockerfile # optional â rebuilt on every run if present
âââ some-app/
âââ docker-compose.yml
âââ Makefile # optional â needs a "test:" target
git clone <this-repo>
cd maj-docker
cp .env.example .env
# edit .env: at minimum set PROJETS_DIR and one notification channel
docker compose build
docker compose up -d
docker compose logs -f docker-update
To trigger a run immediately instead of waiting for UPDATE_TIME, either set
RUN_ON_STARTUP=true in .env, or run it manually once:
docker compose exec --user appuser docker-update /app/update-projets.sh
All variables live in .env (copy .env.example first). Anything left
empty falls back to the default shown, or disables the related feature.
| Variable | Default | Description |
|---|---|---|
PUID | 1000 | UID the container runs as (never root). Get yours with id -u. |
PGID | 1000 | GID the container runs as. Get yours with id -g. |
TZ | Europe/Paris | Timezone used for logs and the scheduler. |
| Variable | Default | Description |
|---|---|---|
PROJETS_DIR | /projets | Absolute path to your projects folder â must match on host and container (see warning above). |
FIND_MAXDEPTH | 3 | How deep to search for docker-compose.yml files under PROJETS_DIR. |
| Variable | Default | Description |
|---|---|---|
LOGS_DIR | ./logs | Host path where logs are persisted (compose bind mount only). |
LOG_RETENTION_DAYS | 30 | Logs older than this are deleted automatically. |
| Variable | Default | Description |
|---|---|---|
UPDATE_TIME | 04:00 | Daily trigger time, HH:MM, in the TZ timezone. |
RUN_ON_STARTUP | false | Also run once immediately when the container starts. |
| Variable | Default | Description |
|---|---|---|
STABILITY_WAIT_SECONDS | 10 | Wait time after a restart before checking containers are still up. |
IMAGE_PRUNE_ENABLED | true | Run docker image prune -af after each pass. |
| Variable | Default | Description |
|---|---|---|
NOTIFY_CHANNELS | email | Comma-separated list, any combination of email,discord,ntfy,telegram,gotify,pushover. |
NOTIFY_PREFIX | [docker-update] | Prefix added to every notification's subject/title. |
A notification only fires when something failed or something was actually updated â an all-skipped run stays silent.
Enable any combination via NOTIFY_CHANNELS. A channel listed there but left
unconfigured (empty URL/token) is skipped with a log line â it never breaks
the run.
| Variable | Description |
|---|---|
EMAIL_TO | Recipient address. Empty = channel disabled. |
SMTP_HOST / SMTP_PORT | Your SMTP server, e.g. smtp.gmail.com / 587. |
SMTP_USER / SMTP_PASSWORD | SMTP credentials (use an app password with Gmail, not your account password). |
SMTP_FROM | From address (defaults to SMTP_USER). |
SMTP_TLS / SMTP_STARTTLS | on/off. |
Credentials are injected at runtime only â ~/.msmtprc is generated by the
entrypoint from these variables, never baked into the image.
| Variable | Description |
|---|---|
DISCORD_WEBHOOK_URL | Empty = channel disabled. |
Get one: Discord channel â Settings â Integrations â Webhooks â New Webhook â copy the URL.
| Variable | Default | Description |
|---|---|---|
NTFY_URL | https://ntfy.sh | Your ntfy server, self-hosted or public. |
NTFY_TOPIC | â | Topic name. Empty = channel disabled. |
NTFY_TOKEN | â | Access token, only if your server requires auth. |
NTFY_PRIORITY | default | min/low/default/high/urgent. |
On the public ntfy.sh, anyone who knows your topic name can read your
messages â pick something hard to guess, or self-host.
| Variable | Description |
|---|---|
TELEGRAM_BOT_TOKEN | Empty = channel disabled. |
TELEGRAM_CHAT_ID | The chat/group that should receive messages. |
Setup: message @BotFather on Telegram, /newbot â gives you the token.
Start a chat with your new bot (or add it to a group), then fetch the chat
ID from https://api.telegram.org/bot<TOKEN>/getUpdates after sending it a
message â look for "chat":{"id":...} in the response.
| Variable | Default | Description |
|---|---|---|
GOTIFY_URL | â | e.g. https://gotify.example.com. Empty = channel disabled. |
GOTIFY_TOKEN | â | Application token, created in the Gotify web UI. |
GOTIFY_PRIORITY | 5 | 0â10. |
| Variable | Default | Description |
|---|---|---|
PUSHOVER_TOKEN | â | Application token from pushover.net. Empty = channel disabled. |
PUSHOVER_USER_KEY | â | Your user (or group) key. |
PUSHOVER_PRIORITY | 0 | -2 to 2. |
PUID/PGID, switched to via su-exec after a short root-only setup
step (permission fixing, .msmtprc generation).ENV/ARG credentials in the
Dockerfile. SMTP and every notification token/URL are injected at
container runtime from .env, and .msmtprc is generated fresh on
each start.docker.sock mount in the update container. The only
container touching the real socket is docker-socket-proxy, and it's
configured with a minimal allow-list (containers, images, networks,
volumes, build, start/stop/restart) â Swarm, secrets, exec, auth and
everything else is explicitly denied.docker-update-net network.| Symptom | Likely cause |
|---|---|
| "đš No project found" notification on every run | PROJETS_DIR isn't mounted, or the path doesn't actually contain any docker-compose.yml within FIND_MAXDEPTH. |
| Other projects' volumes break after an update run | PROJETS_DIR isn't mounted at the same absolute path on host and container â see the DooD warning above. |
| "Permission denied" on the logs folder | PUID/PGID don't match the owner of LOGS_DIR on the host. |
| A notification channel never arrives | Check docker compose logs docker-update â a misconfigured channel logs a clear "ignored" line rather than failing silently. |
| Socket proxy won't start | Some hosts need privileged: true on docker-socket-proxy for AppArmor/SELinux reasons (already set) â try removing it if your host doesn't need it. |
Full logs live at ${LOGS_DIR}/update-docker-<timestamp>.log on the host,
with ${LOGS_DIR}/latest.log always pointing at the most recent run.
Un ordonnanceur conteneurisĂ© et auto-hĂ©bergĂ© qui teste, met Ă jour, reconstruit et redĂ©marre en toute sĂ©curitĂ© vos autres projets Docker Compose â avec un ordonnanceur interne (sans cron de l'hĂŽte), aucune exposition directe du socket Docker, et des notifications multi-canaux (email, Discord, ntfy, Telegram, Gotify, Pushover).
Pointez-le vers un dossier contenant des projets
~/projets/<nom>/docker-compose.yml et il s'occupe du reste : lancer les
tests de chaque projet, comparer les digests d'images avant/aprĂšs pull,
reconstruire les Dockerfile locaux, ne redémarrer que ce qui a changé,
vérifier 10 secondes plus tard que les conteneurs tournent toujours, nettoyer
les images inutilisĂ©es, et vous notifier â uniquement quand quelque chose
s'est réellement passé.
find -L, suit les liens symboliques)
Ă la recherche de docker-compose.yml, Ă une profondeur configurable.test.sh ou make test (si un
Makefile définit une cible test:). Un test qui échoue annule la mise
Ă jour pour ce projet uniquement.docker compose pull â aucun redĂ©marrage si rien
n'a changé.Dockerfile, il est reconstruit à chaque passage.latest.log, avec purge
automatique selon une durée de rétention.UPDATE_TIME), sans dépendre du cron de l'hÎte.PUID/PGID configurables), ne
touche jamais /var/run/docker.sock directement â il passe par un proxy
filtrant Tecnativa docker-socket-proxyâ ..env â
rien n'est codĂ© en dur.âââââââââââââââââââââââââââââ tcp://âŠ:2375 âââââââââââââââââââââââââââââ
â docker-update â ââââââââââââââââââș â docker-socket-proxy â
â (ordonnanceur + logique) â â (filtre l'API Docker) â
â utilisateur non-root â â â
âââââââââââââââââââââââââââââ ââââââââââââââŹââââââââââââââ
â montage ro
âŒ
/var/run/docker.sock
(démon Docker de l'hÎte)
docker-update ne fait jamais tourner son propre démon Docker. Il envoie ses
commandes docker compose par le réseau à docker-socket-proxy, le seul
conteneur autorisĂ© Ă toucher le vrai socket â et uniquement pour les
opĂ©rations qu'il autorise explicitement (voir SĂ©curitĂ©â ).
â ïž Important â c'est du "Docker-outside-of-Docker" : les commandes s'exĂ©cutent depuis
docker-update, mais c'est le dĂ©mon Docker de l'hĂŽte qui les exĂ©cute rĂ©ellement. Quand ledocker-compose.ymld'un de vos projets rĂ©sout un montage relatif ou un contexte de build, c'est l'hĂŽte qui interprĂšte ce chemin â pas le conteneurdocker-update. C'est pourquoiPROJETS_DIRdoit ĂȘtre montĂ© au chemin absolu strictement identique cĂŽtĂ© hĂŽte et cĂŽtĂ© conteneur (ledocker-compose.ymlfait${PROJETS_DIR}:${PROJETS_DIR}). Si les chemins diffĂšrent, les volumes de vos autres projets seront mal rĂ©solus.
/home/vous/projets/
âââ nextcloud/
â âââ docker-compose.yml
â âââ test.sh # optionnel â exit 0 = OK, sinon = annule la mise Ă jour
âââ vaultwarden/
â âââ docker-compose.yml
â âââ Dockerfile # optionnel â reconstruit Ă chaque passage si prĂ©sent
âââ une-appli/
âââ docker-compose.yml
âââ Makefile # optionnel â nĂ©cessite une cible "test:"
git clone <ce-dépÎt>
cd maj-docker
cp .env.example .env
# éditer .env : au minimum PROJETS_DIR et un canal de notification
docker compose build
docker compose up -d
docker compose logs -f docker-update
Pour déclencher une exécution immédiate plutÎt que d'attendre UPDATE_TIME,
soit mettez RUN_ON_STARTUP=true dans .env, soit lancez-la manuellement :
docker compose exec --user appuser docker-update /app/update-projets.sh
Toutes les variables vivent dans .env (copier .env.example d'abord).
Une variable laissée vide reprend la valeur par défaut indiquée, ou
désactive la fonctionnalité concernée.
| Variable | Défaut | Description |
|---|---|---|
PUID | 1000 | UID sous lequel tourne le conteneur (jamais root). à récupérer avec id -u. |
PGID | 1000 | GID sous lequel tourne le conteneur. à récupérer avec id -g. |
TZ | Europe/Paris | Fuseau horaire utilisé pour les logs et l'ordonnanceur. |
| Variable | Défaut | Description |
|---|---|---|
PROJETS_DIR | /projets | Chemin absolu vers votre dossier de projets â doit ĂȘtre identique cĂŽtĂ© hĂŽte et conteneur (voir avertissement ci-dessus). |
FIND_MAXDEPTH | 3 | Profondeur de recherche des docker-compose.yml sous PROJETS_DIR. |
| Variable | Défaut | Description |
|---|---|---|
LOGS_DIR | ./logs | Chemin hĂŽte oĂč les logs sont conservĂ©s (montage compose uniquement). |
LOG_RETENTION_DAYS | 30 | Les logs plus anciens sont supprimés automatiquement. |
| Variable | Défaut | Description |
|---|---|---|
UPDATE_TIME | 04:00 | Heure de déclenchement quotidienne, HH:MM, dans le fuseau TZ. |
RUN_ON_STARTUP | false | Lance aussi une exécution immédiate au démarrage du conteneur. |
| Variable | Défaut | Description |
|---|---|---|
STABILITY_WAIT_SECONDS | 10 | Temps d'attente aprÚs un redémarrage avant de vérifier que les conteneurs tournent toujours. |
IMAGE_PRUNE_ENABLED | true | Exécute docker image prune -af aprÚs chaque passage. |
| Variable | Défaut | Description |
|---|---|---|
NOTIFY_CHANNELS | email | Liste séparée par des virgules, combinaison libre parmi email,discord,ntfy,telegram,gotify,pushover. |
NOTIFY_PREFIX | [docker-update] | Préfixe ajouté au sujet/titre de chaque notification. |
Une notification n'est envoyĂ©e que si quelque chose a Ă©chouĂ© ou a effectivement Ă©tĂ© mis Ă jour â une exĂ©cution oĂč tout est inchangĂ© reste silencieuse.
Activez n'importe quelle combinaison via NOTIFY_CHANNELS. Un canal listé
mais non configuré (URL/jeton vide) est simplement ignoré avec une ligne de
log â il ne bloque jamais l'exĂ©cution.
| Variable | Description |
|---|---|
EMAIL_TO | Adresse destinataire. Vide = canal désactivé. |
SMTP_HOST / SMTP_PORT | Votre serveur SMTP, ex: smtp.gmail.com / 587. |
SMTP_USER / SMTP_PASSWORD | Identifiants SMTP (utiliser un mot de passe d'application avec Gmail, pas le mot de passe du compte). |
SMTP_FROM | Adresse expéditrice (par défaut : SMTP_USER). |
SMTP_TLS / SMTP_STARTTLS | on/off. |
Les identifiants ne sont injectĂ©s qu'au runtime â ~/.msmtprc est gĂ©nĂ©rĂ©
par l'entrypoint à partir de ces variables, jamais intégré à l'image.
| Variable | Description |
|---|---|
DISCORD_WEBHOOK_URL | Vide = canal désactivé. |
Pour l'obtenir : salon Discord â ParamĂštres â IntĂ©grations â Webhooks â Nouveau webhook â copier l'URL.
| Variable | Défaut | Description |
|---|---|---|
NTFY_URL | https://ntfy.sh | Votre serveur ntfy, auto-hébergé ou public. |
NTFY_TOPIC | â | Nom du sujet. Vide = canal dĂ©sactivĂ©. |
NTFY_TOKEN | â | Jeton d'accĂšs, uniquement si votre serveur l'exige. |
NTFY_PRIORITY | default | min/low/default/high/urgent. |
Sur le service public ntfy.sh, quiconque connaĂźt le nom de votre sujet peut
lire vos messages â choisissez un nom difficile Ă deviner, ou auto-hĂ©bergez
votre propre serveur.
| Variable | Description |
|---|---|
TELEGRAM_BOT_TOKEN | Vide = canal désactivé. |
TELEGRAM_CHAT_ID | La conversation/le groupe qui doit recevoir les messages. |
Mise en place : parler Ă @BotFather sur Telegram, /newbot â donne le
jeton. Démarrer une conversation avec ce bot (ou l'ajouter à un groupe), puis
récupérer l'identifiant de conversation via
https://api.telegram.org/bot<TOKEN>/getUpdates aprÚs lui avoir envoyé un
message â chercher "chat":{"id":...} dans la rĂ©ponse.
| Variable | Défaut | Description |
|---|---|---|
GOTIFY_URL | â | ex: https://gotify.example.com. Vide = canal dĂ©sactivĂ©. |
GOTIFY_TOKEN | â | Jeton d'application, créé dans l'interface web Gotify. |
GOTIFY_PRIORITY | 5 | 0 Ă 10. |
| Variable | Défaut | Description |
|---|---|---|
PUSHOVER_TOKEN | â | Jeton d'application depuis pushover.net. Vide = canal dĂ©sactivĂ©. |
PUSHOVER_USER_KEY | â | Votre clĂ© utilisateur (ou de groupe). |
PUSHOVER_PRIORITY | 0 | -2 Ă 2. |
PUID/PGID
configurables, la bascule se faisant via su-exec aprÚs une courte étape
d'initialisation en root (correction des permissions, génération de
.msmtprc).ENV/ARG du
Dockerfile. SMTP et tous les jetons/URL de notification sont injectés au
runtime depuis .env, et .msmtprc est rĂ©gĂ©nĂ©rĂ© Ă chaque dĂ©marrage.docker.sock dans le conteneur de mise Ă
jour. Le seul conteneur qui touche le vrai socket est
docker-socket-proxy, configuré avec une liste minimale d'autorisations
(containers, images, rĂ©seaux, volumes, build, start/stop/restart) â Swarm,
secrets, exec, auth et tout le reste sont explicitement refusés.docker-update-net.| SymptÎme | Cause probable |
|---|---|
| Notification "đš Aucun projet trouvĂ©" Ă chaque exĂ©cution | PROJETS_DIR n'est pas montĂ©, ou ne contient rĂ©ellement aucun docker-compose.yml dans la profondeur FIND_MAXDEPTH. |
| Les volumes d'autres projets cassent aprĂšs une mise Ă jour | PROJETS_DIR n'est pas montĂ© au mĂȘme chemin absolu cĂŽtĂ© hĂŽte et conteneur â voir l'avertissement DooD ci-dessus. |
| "Permission denied" sur le dossier de logs | PUID/PGID ne correspondent pas au propriétaire de LOGS_DIR sur l'hÎte. |
| Un canal de notification n'arrive jamais | Consulter docker compose logs docker-update â un canal mal configurĂ© affiche une ligne "ignorĂ©" claire plutĂŽt que d'Ă©chouer silencieusement. |
| Le proxy de socket ne dĂ©marre pas | Certains hĂŽtes ont besoin de privileged: true sur docker-socket-proxy pour des raisons AppArmor/SELinux (dĂ©jĂ activĂ©) â essayez de le retirer si votre hĂŽte n'en a pas besoin. |
Les logs complets se trouvent dans ${LOGS_DIR}/update-docker-<horodatage>.log
sur l'hĂŽte, avec ${LOGS_DIR}/latest.log qui pointe toujours vers la
derniÚre exécution.
Content type
Image
Digest
sha256:cf64fe6bdâŠ
Size
70 MB
Last updated
2 months ago
docker pull ludix0/docker-update