JDM Casanova
← All posts
5 min read

The Coolify monorepo webhook that silently skipped 3 of 4 apps

When one Coolify monorepo app stays stale, separate webhook filtering from stale output. Here is the diagnosis, forced deploy, and live verification.

coolifymonorepodeploygotchasolo-founder

Coolify monorepo deployments fail in two visibly different ways: no deployment starts, or a deployment finishes while the public app still serves stale output. Check the deployment history and running commit first. If there is no run, inspect the Git webhook and Watch Paths. If the expected commit was deployed, compare the live page with a marker from that commit before forcing a rebuild.

I hit the second symptom in a monorepo that holds four Next.js apps: preparemescours.fr, draftmylesson.com, przygotujlekcje.pl, and creaclases.com. Each app lives in its own directory under apps/. Each has its own Dockerfile under tools/docker/<app>.Dockerfile and a BUILD_APP=<app> build argument. All four point at the same git remote and watch the same main branch.

Quick diagnosis: skipped deploy or stale build?

Use the first observable mismatch to choose the next check:

  • No deployment entry: inspect the Git provider webhook and the app's Watch Paths. A monorepo app needs paths for its own directory plus any shared packages that should trigger it.
  • A deployment ran with the wrong commit: stop at source selection or admission. A rebuild cannot fix an application that checked out the wrong revision.
  • The expected commit is running but the page is stale: compare a specific HTML, asset, or header marker. This separates application output from an intermediary cache.
  • A clean rebuild is needed: use an authenticated Coolify deploy webhook, then repeat the live marker check. A successful dashboard status is not the final proof.

The commit that only reached one app

Commit a33cca4 touched shared layout and CSS. Specifically, it rewired the next/font configuration across all four apps. I pushed, waited a few minutes, and checked the live sites. Creaclases had the new font. The other three did not.

I pulled the live HTML from each site and grepped for the new font URL. Only creaclases had it. I checked Coolify's deployment list for each app. All four said "deployment finished" and showed the same commit SHA. I checked the running Docker containers. Creaclases had an image tag matching the new SHA. The other three had older tags.

The auto-webhook had fired for all four. But only one actually rebuilt.

What the evidence showed in this incident

The deployment history showed a completed run for each app, but the public output did not match the commit. A forced deployment refreshed the three stale apps. That made cached build output the useful working diagnosis for this incident, not a universal explanation for every skipped Coolify monorepo deployment.

The distinction matters because Watch Paths solve a different problem. They determine whether a change should trigger an application deployment. They do not prove that the expected image is running or that the public response contains the new output. Coolify's automatic deployment documentation covers provider webhooks; production verification still needs a marker from the commit you intended to ship.

The fix was a force-triggered deploy

I had to bypass the cache manually. Coolify's API has a force=true parameter that tells the build runner to ignore any cached image and do a full rebuild.

curl -s -X GET 'https://<coolify-host>/api/v1/deploy?uuid=<APP_UUID>&force=true' \
  -H 'Authorization: Bearer <TOKEN>'

I ran that for the three stale apps. Within a few minutes all four had the new font. Total wasted time: 90 minutes of users seeing stale layouts.

The durable fix is deployment verification

After any cross-app commit, I now grep the live HTML of each app for a marker from the commit: a changed string, a new class name, or a different font URL. If a marker is missing, I inspect the running revision and deployment logs before deciding whether to force-trigger that app.

I moved from Vercel to Coolify for cost and control, and I wrote about that migration earlier. This incident did not show that every Coolify monorepo deployment has the same cause. It showed why each app needs both a correct trigger configuration and a live artifact check.

How to verify a monorepo deploy actually took

Here is the workflow I use now.

After a push, I wait for all four Coolify dashboards to show "deployment finished." Then I run a script that fetches each app's homepage and greps for a string I know changed in the commit. If any app is missing the string, I force-trigger that app.

#!/bin/bash
APPS=("preparemescours.fr" "draftmylesson.com" "przygotujlekcje.pl" "creaclases.com")
MARKER="font-family: Inter"

for APP in "${APPS[@]}"; do
  if curl -s "https://$APP" | grep -q "$MARKER"; then
    echo "$APP: OK"
  else
    echo "$APP: MISSING"
  fi
done

The script takes 10 seconds. It saves me from another 90-minute cache blind spot.

The tradeoff I accepted

I could disable Docker caching entirely. That would guarantee every build is fresh. It would also make every deploy slower and more expensive. My apps are small, so the extra build time is maybe 30 seconds per app. But I do not want to burn compute on cache misses for trivial commits that change only one app.

I could also split the monorepo into separate repos. That would eliminate the cross-app cache problem. It would also multiply my CI/CD configuration, secret management, and local development setup by four. I have been down that road with Carriva and Profnova. Monorepo is better for my solo-founder workflow even with this gotcha.

For now, the verification script and the force-trigger recovery are enough for this setup. I check the HTML after every cross-app commit.

Another Coolify gotcha to watch for

This is not the only monorepo gotcha I have hit with Coolify. The GitHub deploy-key reuse issue is another one. Once a public key is attached to one repository as a deploy key, GitHub will not let you attach that key to another repository. I wrote about the error and the per-repository key fix separately.

The pattern is the same. Coolify is a solid tool for a solo founder running multiple apps. But it assumes each app is its own repo. When you run a monorepo, you need to test the edge cases yourself. The dashboard will not tell you when a deploy is a no-op. You have to look at the live output.

The takeaway

Treat "deployment finished" as build status, not proof that the intended code is live. Verify a commit-specific marker after every cross-app deployment. If it is missing, check the running revision, logs, and public response before choosing a forced rebuild.

It is a small cost. The alternative is splitting repos, which has its own costs. I will take the 10-second verification script and move on.