aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Tools & DevOps

Zero Downtime Deploy: gids met symlinks en health checks

Een zero downtime deploy betekent een nieuwe versie uitrollen zonder dat ook maar één gebruiker ooit een foutpagina te zien krijgt. In de klassieke aanpak verbind je met de server, kopieer je bestanden over de live map heen, installeer je dependencies en herstart je de service — maar tijdens die paar seconden serveert de site ofwel half geschreven bestanden, ofwel valt hij volledig uit. In deze gids loop ik stap voor stap door hoe je een naadloze omschakeling opzet voor kleine en middelgrote projecten met niets meer dan een symlink (symbolische link) en een health check, zonder naar een zwaar orkestratietool te grijpen.

Het probleem: waarom "kopiëren en herstarten" downtime veroorzaakt

Bestanden rechtstreeks over de live map heen schrijven brengt twee gevaren met zich mee. Ten eerste: als de kopie wordt onderbroken, serveert de webserver een mix van oude en nieuwe code — wat in PHP fatale fouten, asset-mismatches of half gerenderde templates betekent. Ten tweede duren stappen zoals composer install of het uitvoeren van migraties enkele seconden, en gedurende dat hele venster blijft de applicatie in een inconsistente staat.

De kern van de oplossing is eenvoudig: je installeert de nieuwe versie in een aparte map, en zodra alles klaar is, schakel je in één enkele operatie de link die naar de live map wijst om naar de nieuwe map. Op Linux is het vervangen van een symlink een atomaire operatie — de server ziet ofwel de volledig oude map, ofwel de volledig nieuwe, nooit een mix van beide.

Mapstructuur: releases, shared en current

Er is een beproefde structuur die tools als Capistrano en Deployer al jaren gebruiken. Zet deze indeling op de server op:

/var/www/app/
├── releases/
│   ├── 2026-06-28-101500/
│   └── 2026-06-28-094200/
├── shared/
│   ├── .env
│   └── storage/
└── current -> releases/2026-06-28-101500/

De logica is deze: elke deploy belandt als een nieuwe map met tijdstempel onder releases/. Dingen die niet veranderen tussen versies en bewaard moeten blijven (het environment-bestand, geüploade afbeeldingen, logs) staan onder shared/ en worden via symlinks aan elke release gekoppeld. De document root van de webserver wijst altijd naar de current-link. De omschakeling is simpelweg een kwestie van die current-link verplaatsen.

De deployflow, stap voor stap

De onderstaande volgorde is het skelet van een schone omschakeling. Voer elke stap alleen uit als de vorige is geslaagd:

  • Maak de nieuwe release-map en pak de code daarin uit (git clone, tar pipe of een CI-artefact).
  • Koppel de gedeelde resources: maak symlinks van items zoals .env en storage vanuit shared/ naar de nieuwe release.
  • Installeer dependencies: composer install --no-dev -o, gevolgd door een frontend-build indien nodig.
  • Migraties en cache: als het schema is gewijzigd, draai php artisan migrate --force, daarna php artisan optimize.
  • Voer de health check uit: controleer dat de nieuwe release daadwerkelijk opstart.
  • Schakel de symlink om: alleen als de controle slaagt, laat je current naar de nieuwe map wijzen.

Het cruciale punt: bewaar de symlink voor het laatst. Omdat de trage operaties — bouwen, dependencies installeren, migreren — allemaal plaatsvinden in een map die nog niet live is, hebben gebruikers er nooit last van.

Het hart van de omschakeling: de symlink vervangen

De juiste manier om een symlink ter plekke bij te werken is ln -sfn. De vlag -f overschrijft de bestaande link, terwijl -n verhindert dat de link binnenin het doel wordt aangemaakt wanneer dat doel een map is:

ln -sfn /var/www/app/releases/2026-06-28-101500 /var/www/app/current

Dit ene commando is het moment waarop de hele omschakeling gebeurt. Op de meeste systemen maakt ln het doel eerst onder een tijdelijke naam aan en verplaatst het dan op zijn plek met een rename()-aanroep, en rename() is atomair op hetzelfde bestandssysteem. Na de omschakeling kunnen processen zoals PHP-FPM het oude pad nog in OPcache hebben staan; herlaad PHP-FPM dus daarna netjes (bijvoorbeeld systemctl reload php8.3-fpm) of reset OPcache.

Health check: nooit een kapotte versie promoveren

De laatste barrière vóór de omschakeling is de health check. Het doel is om — vóór het omschakelen van de symlink — te bevestigen dat de nieuwe release daadwerkelijk antwoordt. Voeg een eenvoudig endpoint toe aan je applicatie (bijvoorbeeld /health) en pol het in het deployscript:

URL="http://127.0.0.1/health"
for i in $(seq 1 10); do
  code=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
  if [ "$code" = "200" ]; then
    echo "Health OK"; exit 0
  fi
  sleep 2
done
echo "Health check mislukt, deploy afgebroken"; exit 1

Krijgt het script iets anders dan een 200, dan stopt het met exit 1 en blijft de current-link naar de oude, werkende versie wijzen. Het health-endpoint mag niet oppervlakkig zijn: als het dependencies controleert — een databaseverbinding, toegang tot een kritieke cache — in plaats van alleen "PHP draait" te zeggen, elimineert het het risico op het promoveren van een half kapotte versie.

Rollback en oude releases opruimen

Het mooiste aan deze indeling is dat terugrollen net zo snel is als deployen. Zie je een probleem, dan volstaat het om current terug te laten wijzen naar de vorige release:

ln -sfn /var/www/app/releases/2026-06-28-094200 /var/www/app/current
systemctl reload php8.3-fpm

Zodat oude releases zich niet ophopen op schijf, voeg je een kleine opruimstap toe die de nieuwste 3 tot 5 behoudt en de rest verwijdert. Zo houd je een paar back-ups voor rollback terwijl de schijf nooit volloopt.

Een opmerking over migraties: achterwaarts compatibele wijzigingen

De symlink mag dan atomair zijn, het databaseschema is dat niet. Oude en nieuwe code delen tijdens de omschakeling kort dezelfde tabel. Splits destructieve schemawijzigingen (een kolom verwijderen of hernoemen) daarom in twee fasen: voeg eerst de nieuwe structuur toe en deploy zo dat beide versies blijven werken; ruim daarna, zodra de oude code volledig is uitgefaseerd, de oude kolommen op in een latere deploy. Deze "expand and contract"-aanpak is de verborgen andere helft van echte downtime-loosheid.

Veelgestelde vragen

Heb ik hiervoor Docker of Kubernetes nodig?

Nee. Symlink-gebaseerde deploys werken perfect op een enkele VPS of shared hosting, helemaal zonder containers. Docker en Kubernetes voegen waarde toe zodra je opschaalt en meerdere servers of replica's moet beheren, maar voor kleine tot middelgrote projecten is de symlink-aanpak veel eenvoudiger en volledig toereikend.

Heb ik echt een apart health-check-endpoint nodig?

De homepage pollen werkt ook, maar een speciaal /health-endpoint is te verkiezen. Het kan bewust dependencies controleren (DB, cache), lichtgewicht blijven en bewaakt worden zonder ruis in je logs te veroorzaken.

Waarom PHP-FPM herladen na het omschakelen van de symlink?

OPcache kan bestanden cachen op hun opgeloste echte paden. Zelfs na het omschakelen van de link kunnen processen de oude code blijven serveren. Een reload (geen restart) vernieuwt die cache, en doet dat zonder downtime te veroorzaken.

Wil je een deploypijplijn zonder downtime? Of het nu Laravel is of een andere stack, ik kan je helpen om op je bestaande server een symlink-gebaseerde deployflow op te zetten, met health check en rollback met één enkel commando. Neem contact op en laten we het over je project hebben.

Bu kategorideki tüm yazılar →

Devamı için