[{"data":1,"prerenderedAt":569},["ShallowReactive",2],{"navigation":3,"/blog/github-actions-laravel-zero-downtime-deployment":26,"/blog/github-actions-laravel-zero-downtime-deployment-surround":565},[4],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Blog","/blog","blog",[9,13,17,21],{"title":10,"path":11,"stem":12},"GitHub Actions CI/CD for Laravel: A Zero-Downtime Deployment Setup","/blog/github-actions-laravel-zero-downtime-deployment","blog/github-actions-laravel-zero-downtime-deployment",{"title":14,"path":15,"stem":16},"Integrating LLMs into Laravel Using Python Microservices","/blog/integrating-llms-into-laravel-python-microservices","blog/integrating-llms-into-laravel-python-microservices",{"title":18,"path":19,"stem":20},"How I Cut Report Generation from 30s to 2s in Laravel","/blog/laravel-report-generation-30s-to-2s","blog/laravel-report-generation-30s-to-2s",{"title":22,"path":23,"stem":24},"Why I Chose PostgreSQL Over MongoDB for a CRM Reporting Engine","/blog/postgresql-vs-mongodb-crm-reporting","blog/postgresql-vs-mongodb-crm-reporting",false,{"id":27,"title":10,"author":28,"body":32,"date":557,"description":558,"extension":559,"image":560,"meta":561,"minRead":146,"navigation":102,"path":11,"seo":562,"sitemap":563,"stem":12,"__hash__":564},"blog/blog/github-actions-laravel-zero-downtime-deployment.md",{"name":29,"avatar":30},"Vincent Ndegwa",{"src":31,"alt":29},"/personal/profile.jpeg",{"type":33,"value":34,"toc":549},"minimark",[35,39,42,47,63,67,70,381,385,388,399,402,442,445,449,452,517,520,524,535,539,542,545],[36,37,38],"p",{},"I have seen great Laravel features fail for one simple reason: deployment was fragile. The code was correct, but release day was stressful because the process relied on manual SSH steps and luck.",[36,40,41],{},"This is the setup I now use to make releases boring, predictable, and reversible.",[43,44,46],"h2",{"id":45},"deployment-requirements","Deployment Requirements",[48,49,50,54,57,60],"ul",{},[51,52,53],"li",{},"test and deploy from GitHub Actions",[51,55,56],{},"no downtime for users",[51,58,59],{},"rollback in under one minute",[51,61,62],{},"same approach works for Laravel, Django, and FastAPI services",[43,64,66],{"id":65},"ci-pipeline-first","CI Pipeline First",[36,68,69],{},"Every push to main triggers tests. Deploy only runs if tests pass.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","name: Deploy\n\non:\n  push:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: shivammathur/setup-php@v2\n        with:\n          php-version: '8.4'\n      - run: composer install --no-interaction --prefer-dist\n      - run: cp .env.testing .env && php artisan key:generate\n      - run: php artisan test --parallel\n\n  deploy:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: appleboy/ssh-action@v1\n        with:\n          host: ${{ secrets.DEPLOY_HOST }}\n          username: ${{ secrets.DEPLOY_USER }}\n          key: ${{ secrets.DEPLOY_SSH_KEY }}\n          script: /var/www/scripts/deploy.sh\n","yaml","",[78,79,80,97,104,114,122,139,144,152,160,171,179,193,205,213,230,243,255,267,272,280,291,300,307,318,330,337,348,359,370],"code",{"__ignoreMap":76},[81,82,85,89,93],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"swJcz","name",[81,90,92],{"class":91},"sMK4o",":",[81,94,96],{"class":95},"sfazB"," Deploy\n",[81,98,100],{"class":83,"line":99},2,[81,101,103],{"emptyLinePlaceholder":102},true,"\n",[81,105,107,111],{"class":83,"line":106},3,[81,108,110],{"class":109},"sfNiH","on",[81,112,113],{"class":91},":\n",[81,115,117,120],{"class":83,"line":116},4,[81,118,119],{"class":87},"  push",[81,121,113],{"class":91},[81,123,125,128,130,133,136],{"class":83,"line":124},5,[81,126,127],{"class":87},"    branches",[81,129,92],{"class":91},[81,131,132],{"class":91}," [",[81,134,135],{"class":95},"main",[81,137,138],{"class":91},"]\n",[81,140,142],{"class":83,"line":141},6,[81,143,103],{"emptyLinePlaceholder":102},[81,145,147,150],{"class":83,"line":146},7,[81,148,149],{"class":87},"jobs",[81,151,113],{"class":91},[81,153,155,158],{"class":83,"line":154},8,[81,156,157],{"class":87},"  test",[81,159,113],{"class":91},[81,161,163,166,168],{"class":83,"line":162},9,[81,164,165],{"class":87},"    runs-on",[81,167,92],{"class":91},[81,169,170],{"class":95}," ubuntu-latest\n",[81,172,174,177],{"class":83,"line":173},10,[81,175,176],{"class":87},"    steps",[81,178,113],{"class":91},[81,180,182,185,188,190],{"class":83,"line":181},11,[81,183,184],{"class":91},"      -",[81,186,187],{"class":87}," uses",[81,189,92],{"class":91},[81,191,192],{"class":95}," actions/checkout@v4\n",[81,194,196,198,200,202],{"class":83,"line":195},12,[81,197,184],{"class":91},[81,199,187],{"class":87},[81,201,92],{"class":91},[81,203,204],{"class":95}," shivammathur/setup-php@v2\n",[81,206,208,211],{"class":83,"line":207},13,[81,209,210],{"class":87},"        with",[81,212,113],{"class":91},[81,214,216,219,221,224,227],{"class":83,"line":215},14,[81,217,218],{"class":87},"          php-version",[81,220,92],{"class":91},[81,222,223],{"class":91}," '",[81,225,226],{"class":95},"8.4",[81,228,229],{"class":91},"'\n",[81,231,233,235,238,240],{"class":83,"line":232},15,[81,234,184],{"class":91},[81,236,237],{"class":87}," run",[81,239,92],{"class":91},[81,241,242],{"class":95}," composer install --no-interaction --prefer-dist\n",[81,244,246,248,250,252],{"class":83,"line":245},16,[81,247,184],{"class":91},[81,249,237],{"class":87},[81,251,92],{"class":91},[81,253,254],{"class":95}," cp .env.testing .env && php artisan key:generate\n",[81,256,258,260,262,264],{"class":83,"line":257},17,[81,259,184],{"class":91},[81,261,237],{"class":87},[81,263,92],{"class":91},[81,265,266],{"class":95}," php artisan test --parallel\n",[81,268,270],{"class":83,"line":269},18,[81,271,103],{"emptyLinePlaceholder":102},[81,273,275,278],{"class":83,"line":274},19,[81,276,277],{"class":87},"  deploy",[81,279,113],{"class":91},[81,281,283,286,288],{"class":83,"line":282},20,[81,284,285],{"class":87},"    needs",[81,287,92],{"class":91},[81,289,290],{"class":95}," test\n",[81,292,294,296,298],{"class":83,"line":293},21,[81,295,165],{"class":87},[81,297,92],{"class":91},[81,299,170],{"class":95},[81,301,303,305],{"class":83,"line":302},22,[81,304,176],{"class":87},[81,306,113],{"class":91},[81,308,310,312,314,316],{"class":83,"line":309},23,[81,311,184],{"class":91},[81,313,187],{"class":87},[81,315,92],{"class":91},[81,317,192],{"class":95},[81,319,321,323,325,327],{"class":83,"line":320},24,[81,322,184],{"class":91},[81,324,187],{"class":87},[81,326,92],{"class":91},[81,328,329],{"class":95}," appleboy/ssh-action@v1\n",[81,331,333,335],{"class":83,"line":332},25,[81,334,210],{"class":87},[81,336,113],{"class":91},[81,338,340,343,345],{"class":83,"line":339},26,[81,341,342],{"class":87},"          host",[81,344,92],{"class":91},[81,346,347],{"class":95}," ${{ secrets.DEPLOY_HOST }}\n",[81,349,351,354,356],{"class":83,"line":350},27,[81,352,353],{"class":87},"          username",[81,355,92],{"class":91},[81,357,358],{"class":95}," ${{ secrets.DEPLOY_USER }}\n",[81,360,362,365,367],{"class":83,"line":361},28,[81,363,364],{"class":87},"          key",[81,366,92],{"class":91},[81,368,369],{"class":95}," ${{ secrets.DEPLOY_SSH_KEY }}\n",[81,371,373,376,378],{"class":83,"line":372},29,[81,374,375],{"class":87},"          script",[81,377,92],{"class":91},[81,379,380],{"class":95}," /var/www/scripts/deploy.sh\n",[43,382,384],{"id":383},"atomic-release-strategy","Atomic Release Strategy",[36,386,387],{},"The server keeps timestamped releases:",[48,389,390,393,396],{},[51,391,392],{},"/var/www/releases/20260330114500",[51,394,395],{},"/var/www/releases/20260330121810",[51,397,398],{},"/var/www/current -> symlink to active release",[36,400,401],{},"New code is prepared in a fresh folder. Only after migrations, cache warmup, and health checks pass do we switch the symlink.",[71,403,407],{"className":404,"code":405,"language":406,"meta":76,"style":76},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","ln -sfn /var/www/releases/$RELEASE /var/www/current\nsudo systemctl reload php8.4-fpm\n","bash",[78,408,409,428],{"__ignoreMap":76},[81,410,411,415,418,421,425],{"class":83,"line":84},[81,412,414],{"class":413},"sBMFI","ln",[81,416,417],{"class":95}," -sfn",[81,419,420],{"class":95}," /var/www/releases/",[81,422,424],{"class":423},"sTEyZ","$RELEASE ",[81,426,427],{"class":95},"/var/www/current\n",[81,429,430,433,436,439],{"class":83,"line":99},[81,431,432],{"class":413},"sudo",[81,434,435],{"class":95}," systemctl",[81,437,438],{"class":95}," reload",[81,440,441],{"class":95}," php8.4-fpm\n",[36,443,444],{},"The symlink change is atomic, so active requests complete safely while new requests hit the new release.",[43,446,448],{"id":447},"rollback-procedure","Rollback Procedure",[36,450,451],{},"If health checks fail after deploy:",[71,453,455],{"className":404,"code":454,"language":406,"meta":76,"style":76},"PREV=$(ls -t /var/www/releases | sed -n '2p')\nln -sfn /var/www/releases/$PREV /var/www/current\nsudo systemctl reload php8.4-fpm\n",[78,456,457,494,507],{"__ignoreMap":76},[81,458,459,462,465,468,471,474,477,480,483,485,488,491],{"class":83,"line":84},[81,460,461],{"class":423},"PREV",[81,463,464],{"class":91},"=$(",[81,466,467],{"class":413},"ls",[81,469,470],{"class":95}," -t",[81,472,473],{"class":95}," /var/www/releases",[81,475,476],{"class":91}," |",[81,478,479],{"class":413}," sed",[81,481,482],{"class":95}," -n",[81,484,223],{"class":91},[81,486,487],{"class":95},"2p",[81,489,490],{"class":91},"'",[81,492,493],{"class":91},")\n",[81,495,496,498,500,502,505],{"class":83,"line":99},[81,497,414],{"class":413},[81,499,417],{"class":95},[81,501,420],{"class":95},[81,503,504],{"class":423},"$PREV ",[81,506,427],{"class":95},[81,508,509,511,513,515],{"class":83,"line":106},[81,510,432],{"class":413},[81,512,435],{"class":95},[81,514,438],{"class":95},[81,516,441],{"class":95},[36,518,519],{},"No panic, no patching in production. Just revert, recover, and inspect logs.",[43,521,523],{"id":522},"secrets-and-environment-safety","Secrets and Environment Safety",[48,525,526,529,532],{},[51,527,528],{},"keep .env outside release folders",[51,530,531],{},"inject secrets via GitHub encrypted secrets",[51,533,534],{},"never expose internal AI services directly to the public network",[43,536,538],{"id":537},"why-this-matters-for-ai-features-too","Why This Matters for AI Features Too",[36,540,541],{},"If your Laravel app depends on FastAPI AI services, release reliability matters even more. Coordinated deploys and quick rollback protect both your core product and AI flows.",[36,543,544],{},"A stable CI/CD system is not just DevOps polish. It is product reliability, customer trust, and engineering sanity.",[546,547,548],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":76,"searchDepth":99,"depth":99,"links":550},[551,552,553,554,555,556],{"id":45,"depth":99,"text":46},{"id":65,"depth":99,"text":66},{"id":383,"depth":99,"text":384},{"id":447,"depth":99,"text":448},{"id":522,"depth":99,"text":523},{"id":537,"depth":99,"text":538},"2025-01-28","A practical walkthrough of atomic symlink deployments, parallel test pipelines, and one-command rollbacks using GitHub Actions and DigitalOcean.","md","https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?w=1260&auto=format&fit=crop&q=80",{},{"title":10,"description":558},{"loc":11},"J9o1fJXTq7YJJW0-vOcPkZavzP5okO_n7xzVAI-u7HI",[566,567],null,{"title":14,"path":15,"stem":16,"description":568,"children":-1},"A practical guide to wiring a LangChain Python service to a Laravel backend so you can add AI-powered features without abandoning your existing stack.",1791651066261]