[{"data":1,"prerenderedAt":169},["ShallowReactive",2],{"seo-verification":3,"blog-docker-compose-update-best-practices-vps-ma-en":6,"promo-bar-likely":168},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-docker-compose-update-best-practices-vps-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":29,"intro":32,"sections":33,"ctaTitle":109,"ctaBody":110,"ctaButton":111,"ctaUrl":112,"relatedPosts":113},427,"docker-compose-update-best-practices-vps",{"fr":12,"en":10,"ar":13,"es":14},"docker-compose-mise-a-jour-best-practices-vps","أفضل-ممارسات-تحديث-docker-compose-على-vps","docker-compose-actualizacion-buenas-practicas-vps","Updating a Docker Compose stack in production","A complete protocol to update Docker Compose stacks without downtime or data loss: version pinning, volume backup, and two-minute rollback.",10,6,false,"2026-10-09T00:00:00+00:00","2026-11-08T01:13:13+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},3,"Deployment","deploiement","bg-success\u002F10 text-success",[28],{"id":23,"name":24,"slug":25,"color":26,"icon":25},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fdocker-compose-mise-a-jour-best-practices-vps-poster.svg","A `docker compose pull` followed by `up -d` has always worked — until it doesn't. The compatibility breaks in Meilisearch, Supabase (PostgreSQL 15→17) and Langfuse (v3→v4) are a reminder: a stable instance that has run in production for months can refuse to start, or reject its incoming data, after an unprepared update. This guide gives you a repeatable protocol — back up before you pull, check release notes before restarting, and roll back to the previous image in under two minutes if something breaks.",[34,38,46,49,68,72,75,103,106],{"type":35,"title":36,"body":37},"h2","Why `latest` is the real culprit","When a Docker image tagged `latest` is updated in the registry, your `docker compose pull` downloads it silently. No warning, no diff. You restart the stack and discover the new container cannot read the data left by the old one.\n\nThis is exactly the risk with **Meilisearch**. The database carries the version number of the engine that wrote it: on a version upgrade, the new engine refuses to open it until you explicitly ask it to migrate with `--upgrade-db` (or the `MEILI_UPGRADE_DB` variable). With a restart policy, the container then enters a crash-loop. And once the database has been migrated, the old image can no longer open it: Meilisearch does not support downgrades. The only clean safety net is a dump or a volume snapshot taken before the update — impossible once the database is migrated or the container is stuck.\n\nThe `latest` tag does not resolve to the same image depending on when you pull. Two developers running `docker compose pull` twelve hours apart may pull different versions. On a production stack, this ambiguity is unacceptable. The solution is not to avoid updates: it is to **explicitly control which version is running** and to consciously decide when to move to the next one.",{"type":39,"title":40,"items":41},"ul","What this guide covers — and what it does not",[42,43,44,45],"**What this guide covers**: a step-by-step protocol to update a Docker Compose stack on a VPS — version pinning, volume backup, release notes review, fast rollback, healthchecks as a safety net.","**Covered elsewhere**: the initial hardening checklist (`docker-compose-production-checklist`), configuring `depends_on` and `service_healthy` (`docker-compose-depends-on-healthcheck`), and comparing auto-update tools like Watchtower or Diun.","**What this guide does not recommend**: Watchtower or any auto-pull tool in production — that is precisely the anti-pattern the breaking change cases illustrate.","**Target audience**: developers and agencies managing one or more Docker Compose stacks in production on a VPS, with root access and persistent volumes.",{"type":35,"title":47,"body":48},"Step 1 — Pin all your images","The first action, before any update, is to replace every `image: getmeili\u002Fmeilisearch:latest` or `image: supabase\u002Fpostgres` with an explicit version.\n\nTwo forms are acceptable:\n\n- **Version tag**: `image: getmeili\u002Fmeilisearch:v1.53.0` — readable, versionable in git, easy to patch.\n- **SHA256 digest**: `image: getmeili\u002Fmeilisearch@sha256:abc123…` — immutable, guarantees you pull exactly the same artifact on every redeploy, even if the tag has been overwritten (which happens on public registries).\n\nTo get the digest of an already-running image:\n\n```bash\ndocker inspect --format='{{index .RepoDigests 0}}' getmeili\u002Fmeilisearch:v1.53.0\n```\n\nOnce your images are pinned, commit `docker-compose.yml` to git. Every version bump becomes a commit, giving you a clear history and a trivial rollback (`git revert` + `docker compose up -d`).",{"type":50,"title":51,"steps":52},"steps","Update protocol — the 5 steps",[53,56,59,62,65],{"title":54,"body":55},"Read the release notes before pulling","First, check the release notes for the new version. Look for the words `breaking`, `migration`, `incompatible`, `pg_upgrade`, `dump`. This is not optional: Supabase explicitly documented that upgrading from PostgreSQL 15 to 17 **requires a manual `pg_upgrade`** — the PG 17 container refuses to start on a PG 15 volume, and the initialization process does not automatically migrate data.\n\nFor Langfuse v4 (v4.0.0 released July 29, 2026), Python SDK v2 and older are **rejected at ingestion** by the new stack — a breaking change that affects all client services tracing via the old API.\n\nThree minutes of reading saves several hours of data recovery.",{"title":57,"body":58},"Back up volumes before pulling","Never pull before having a usable backup. For named volumes, two approaches:\n\n**Application dump (recommended for databases)** — the service must be `healthy` before dumping:\n\n```bash\ndocker compose exec -T db pg_dump -U postgres -Fc mydb > backup_$(date +%Y%m%d_%H%M%S).dump\n```\n\nThe `-T` option disables the pseudo-terminal: without it, the binary dump stream can be altered.\n\n**Raw volume snapshot** — useful for binary stores (Meilisearch, Redis, MinIO):\n\n```bash\ndocker run --rm \\\n  --volumes-from $(docker compose ps -q meilisearch) \\\n  -v $(pwd)\u002Fbackups:\u002Fbackup \\\n  alpine tar czf \u002Fbackup\u002Fmeili_$(date +%Y%m%d_%H%M%S).tar.gz \u002Fmeili_data\n```\n\nVerify the backup is readable before proceeding. A corrupt dump file discovered during recovery is the most expensive scenario there is.",{"title":60,"body":61},"Pull the new image and test offline","Update the tag in your `docker-compose.yml`, then pull the image without restarting the service:\n\n```bash\ndocker compose pull meilisearch\n```\n\nIf your environment allows it, test the new image on a volume clone in a ddev environment or a staging VM before touching production. Check the startup logs for any migration errors:\n\n```bash\ndocker compose up -d meilisearch\ndocker compose logs -f meilisearch\n```\n\nWait for the healthcheck to reach `healthy` before validating. A service that starts but is not yet `healthy` is not a ready service.",{"title":63,"body":64},"Check healthchecks","A well-configured healthcheck is your first detection line. It must be present on every critical service in the stack, in Compose v2 format:\n\n```bash\nhealthcheck:\n  test: [\"CMD-SHELL\", \"curl -sf http:\u002F\u002Flocalhost:7700\u002Fhealth || exit 1\"]\n  interval: 10s\n  timeout: 5s\n  retries: 5\n  start_period: 30s\n```\n\nThe `start_period` field is critical for slow-starting services (databases, search engines): it prevents Docker from declaring the container `unhealthy` during the initialization phase and triggering a premature restart.\n\nSee `docker-compose-depends-on-healthcheck` for the full `service_healthy` configuration on PostgreSQL — the same principle applies to any service that needs a warm-up period.",{"title":66,"body":67},"Roll back if something breaks","If the new version fails to start or produces errors, rollback must take under two minutes. The procedure:\n\n1. Revert to the previous tag in `docker-compose.yml` (or `git revert` if you committed the bump).\n2. Restart only the affected service, without recreating volumes:\n\n```bash\ndocker compose up -d --no-deps --force-recreate meilisearch\n```\n\n3. Check the logs immediately:\n\n```bash\ndocker compose logs -f meilisearch\n```\n\nThe `--no-deps` flag is essential: it restarts the target service without touching other containers (database, cache, proxy). Without it, `docker compose up -d` may recreate the entire stack.\n\n⚠️ If the new version migrated the on-disk data format (Meilisearch after `--upgrade-db`, Supabase PG17), rolling back the image is not enough — which is why the volume backup is a precondition, not an option.",{"type":69,"title":70,"body":71},"tip","Keep the previous version available locally","Before pulling the new image, tag the currently-running image under a retention name:\n\n```bash\ndocker tag getmeili\u002Fmeilisearch:v1.53.0 getmeili\u002Fmeilisearch:rollback\n```\n\nThis lets you return to the exact production state in an emergency, even if you no longer have registry access or the connection is slow. On a VPS with limited bandwidth, this local tag saves several minutes of download time at the worst moment.",{"type":35,"title":73,"body":74},"Recent breaking changes that took down instances","These four examples illustrate why the protocol above is not theoretical.\n\n**Meilisearch** (on a version upgrade): a newer engine refuses to open a database written by an older version, with `Your database version (…) is incompatible with your current engine version`, until `--upgrade-db` is passed. That migration can change the on-disk format: in v1.37.0 (March 2026), vector indexes still in the arroy format were converted to the new HNSW store (hannoy). And downgrades are not supported (`Downgrade is not supported`). The only clean way out after a failure is to have exported a dump before the update, then import it (`meilisearch --db-path \u002Fmeili_data --import-dump \u002Fbackup.dump`) — possible only if you exported before updating.\n\n**Supabase Docker PostgreSQL 15 → 17** (PG 17 default from June 17, 2026): the `supabase\u002Fpostgres:17` container cannot read a volume initialized by PG 15. Supabase explicitly documents that the jump requires a `pg_upgrade` via a dedicated script — the container initialization process does not do it automatically. Without a prior migration, the database does not start.\n\n**Langfuse v3 → v4** (v4.0.0 released July 29, 2026): v4 drops the legacy batch ingestion endpoints in favor of OpenTelemetry. Python SDK v2 and older and JS\u002FTS SDK v3 and older are **rejected at ingestion** as soon as the v4 stack runs in its default write mode (`events_only`); the `legacy` and `dual` modes let you stage the migration. If your client services have not migrated before the server update, they silently lose all their traces.\n\n**NocoDB (2026.09 series)**: no broken database, but an incompatible change announced in advance. The 2026.09.0 notes repeat that datetimes returned by the REST API, webhooks and exports will move from second to millisecond precision on PostgreSQL and SQLite in an upcoming release. A client that parses dates with a strict format will break without the server reporting anything — exactly what reading the release notes lets you anticipate. Another trap, independent of the version: a bind-mount installation (`.\u002Fpostgres`, `.\u002Fnocodb`) whose mount path changes between two deployments starts on an empty database. Named volumes prevent this drift.",{"type":76,"title":77,"headers":78,"rows":83},"comparison","Update strategies: comparison",[79,80,81,82],"Strategy","Data safety","Prep time","Rollback",[84,89,94,99],[85,86,87,88],"`docker compose pull` + `up -d` direct","No guarantee — breaking changes undetected","\u003C 1 minute","Difficult if data was migrated",[90,91,92,93],"Versioned tag bump + volume backup","High — data saved before any change","10 to 20 minutes","Trivial: revert tag + `up -d --no-deps`",[95,96,97,98],"Staging test before prod","Maximum — breaking changes caught outside prod","Varies by environment","Not needed if test passed",[100,101,102,102],"Image pinned by SHA256 digest","High — immune to tag-overwrite","Same as versioned tag",{"type":35,"title":104,"body":105},"Integrating this protocol into your workflow","A protocol that stays in a guide is useless. To apply it systematically, externalize the version into a versioned `.env` file committed to git:\n\n```bash\n# .env\nMEILISEARCH_VERSION=v1.53.0\nPOSTGRES_VERSION=15.6\n```\n\n```bash\n# docker-compose.yml\nservices:\n  meilisearch:\n    image: getmeili\u002Fmeilisearch:${MEILISEARCH_VERSION}\n```\n\nUpdating a version then becomes a single commit on `.env` — readable in `git log`, reversible with `git revert`, and deployable by CI\u002FCD without modifying the main Compose file.\n\nFor agencies managing multiple client stacks, create an `INFRA_CHANGELOG.md` per client: every update is traced with the previous version, date, backup taken, and outcome. This also protects you contractually in the event of a subsequent incident.",{"type":69,"title":107,"body":108},"Automate without losing control","If you want to be notified of new versions without auto-pulling, **Diun** (Docker Image Update Notifier) monitors your registry and sends you a notification (Slack, email, webhook) when a new image is available. You remain in control of when to update.\n\nThis is the fundamental difference from Watchtower: Diun notifies, Watchtower acts. On a production stack with persistent volumes, notification is the right level of automation — the action stays manual and preceded by the protocol above.","A VPS with root access to apply this protocol","Full dumps, snapshots before updates, rollback to a previous image: this protocol requires root access and controllable local storage. Shared hosting does not give you this level of control over Docker volumes.","See Cloud VPS plans","\u002Fvps-cloud",[114,131,148],{"id":115,"slug":116,"slugs":117,"title":121,"excerpt":122,"readTime":123,"views":124,"isPinned":19,"noindex":19,"publishedAt":125,"updatedAt":126,"category":127,"categories":128,"featuredImage":29,"bgImage":30,"posterImage":130,"relatedSolution":29},229,"docker-compose-in-production-10-point-checklist",{"fr":118,"en":116,"ar":119,"es":120},"docker-compose-production-checklist","docker-compose-في-الإنتاج-قائمة-التحقق-من-10-نقاط","checklist-docker-compose-en-produccion","Docker Compose in Production: 10-Point Checklist","Docker Compose in production: 10 settings, health checks, secrets, rollback, troubleshooting and moving from docker-compose v1 to docker compose on Ubuntu 26.04.",12,1,"2026-08-06T00:00:00+00:00","2026-10-23T13:27:09+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[129],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdocker-compose-production-checklist-poster.svg",{"id":132,"slug":133,"slugs":134,"title":138,"excerpt":139,"readTime":140,"views":141,"isPinned":19,"noindex":19,"publishedAt":142,"updatedAt":143,"category":144,"categories":145,"featuredImage":29,"bgImage":30,"posterImage":147,"relatedSolution":29},282,"depends-on-is-not-enough-postgresql-healthcheck-in-compose",{"fr":135,"en":133,"ar":136,"es":137},"docker-compose-depends-on-healthcheck","depends-on-لا-يكفي-healthcheck-لـ-postgresql-في-compose","docker-compose-healthcheck-postgresql","depends_on is not enough: PostgreSQL healthcheck in Compose","Why `depends_on` alone does not guarantee PostgreSQL is ready, and how to configure a reliable healthcheck with `service_healthy` to avoid race conditions.",8,0,"2026-08-19T00:00:00+00:00","2026-09-07T11:26:10+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[146],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdocker-compose-depends-on-healthcheck-poster.svg",{"id":149,"slug":150,"slugs":151,"title":155,"excerpt":156,"readTime":17,"views":157,"isPinned":19,"noindex":19,"publishedAt":158,"updatedAt":159,"category":160,"categories":165,"featuredImage":29,"bgImage":30,"posterImage":167,"relatedSolution":29},382,"vps-automatic-security-updates-debian-ubuntu",{"fr":152,"en":150,"ar":153,"es":154},"securite-vps-mises-a-jour-automatiques-debian-ubuntu","تحديثات-أمان-تلقائية-vps-debian-ubuntu","actualizaciones-seguridad-vps-debian-ubuntu","Automatic Security Updates on Debian\u002FUbuntu VPS","Configure unattended-upgrades on your Debian\u002FUbuntu VPS to automate security patches and reduce attack surface across your client fleet.",2,"2026-09-26T00:00:00+00:00","2026-11-09T20:55:15+00:00",{"id":140,"name":161,"slug":162,"color":163,"icon":164},"Security & Monitoring","securite-monitoring","bg-rose-500\u002F10 text-rose-400","security",[166],{"id":140,"name":161,"slug":162,"color":163,"icon":164},"\u002Fblog\u002Fcovers\u002Fsecurite-vps-mises-a-jour-automatiques-debian-ubuntu-poster.svg",true,1794270396160]