March 18, 2026 (3mo ago)
Written by David Viejo
Last updated March 18, 2026 (3mo ago)
A git-push deployment is a 7-step pipeline: webhook fires, build queues, repo clones, container builds, health check runs, traffic swaps, old container stops. Every platform that does git push to deploy — Vercel, Heroku, Coolify, Dokploy, or a self-hosted tool like Temps — runs this same pipeline. The differences are in speed, traffic swap correctness, and how much you configure manually.
Understanding each step cuts debugging time in half. When a deploy fails, it's almost always the build, the health check, or the proxy — and knowing which step lets you go straight to the right log.
TL;DR: Every git-push deployment follows the same 7-step pipeline: webhook, build queue, clone, container build, health check, traffic swap, cleanup. The differences between platforms are in speed, traffic swap correctness, and how much you configure manually. When a deploy fails, it's almost always the build, the health check, or the proxy.
When you git push, the platform receives a webhook, queues a build, clones the repo at the exact commit SHA, builds a Docker image, health-checks the new container, then flips the proxy to route traffic to it — all within 60–90 seconds on a typical app. The old container stays live until the health check passes, giving you zero-downtime deploys.
When you push to a repository on GitHub, GitLab, or Gitea, the platform sends an HTTP POST to any webhooks registered for that repo. The payload includes the commit SHA, the branch name, the repository URL, and the pusher's identity.
Your deployment platform registers one of these webhooks when you connect a repository. On Vercel, it happens automatically when you import a project. On Coolify, Dokploy, or a self-hosted tool, you configure it from the dashboard during project setup.
The webhook request is just an HTTP POST. Your deployment server needs a public IP and an open port to receive it. This is why deployment platforms need to be accessible from the internet, not just from your private network.
One thing worth knowing: GitHub will retry a webhook if your server doesn't respond with a 2xx status within 10 seconds. If your build system is slow to acknowledge, you can end up with duplicate builds. Well-built platforms deduplicate by commit SHA.
The deployment server receives the webhook, parses the payload, and queues a build job. It stores the commit SHA, the branch, and a pointer to the repository.
Queuing matters because pushes can come faster than builds complete. If two developers push within 30 seconds of each other, the second push should wait for the first build to finish (or cancel it, depending on the platform's configuration). Naively triggering a concurrent build for every push causes resource contention and race conditions on the traffic swap.
Platforms handle this differently: Vercel runs builds in parallel on separate infrastructure. Coolify queues them on your server. The right behavior depends on whether you have enough build capacity to run concurrently.
Temps-specific: Temps automatically cancels stale in-progress deployments when a newer commit arrives on the same branch. You can also enable manual stale-deploy cancellation from the dashboard — no more waiting for an obsolete build to finish before the new commit deploys.
The build agent clones the repository at the specific commit SHA. This is always a specific SHA, not just the branch head, because the branch might advance between when the webhook fired and when the build starts.
After cloning, the build system detects how to build the app. There are two common approaches:
Dockerfile detection. If a Dockerfile is present at the root, use it. The developer has already specified the build process. This is the most explicit option and the least surprising.
Buildpacks. If there's no Dockerfile, Cloud Native Buildpacks (CNB) scan the repository for language indicators: a package.json suggests Node.js, a requirements.txt suggests Python, a go.mod suggests Go. The matching buildpack downloads the right runtime, installs dependencies, and produces a container image. Heroku pioneered this model; the CNB specification standardized it. Nixpacks is a newer alternative used by Railway that takes a similar detect-and-build approach with Nix-based reproducible builds.
The advantage of buildpacks: you push code without a Dockerfile and the platform figures it out. The downside: if your app needs something non-standard, the buildpack's defaults might not be right, and debugging why takes longer than just writing a Dockerfile.
The actual build runs inside Docker (or a Docker-compatible builder like BuildKit). For a Node.js app, this means npm ci followed by your build command. For a Python app, pip install. For a Go binary, go build.
Build logs stream in real time to the dashboard. This is worth appreciating: you're watching exactly what would happen if you ran docker build locally. If the build fails because a dependency version is missing or an environment variable is undefined, the error is right there in the log.
A detail that matters for build speed: Docker layer caching. A well-structured Dockerfile copies package.json and runs npm install before copying the rest of the source code. That way, the installed dependencies layer gets cached between builds, and only the application code layer gets rebuilt on each push. A poorly structured Dockerfile invalidates the cache on every build. The difference is 30 seconds versus 4 minutes for a typical Node.js app.
Before the new container gets any production traffic, most deployment platforms run a health check. The new container starts, and the platform pings a health endpoint and waits for a 200 response.
This step is what makes zero-downtime deployment possible. The old container keeps serving requests while the new one warms up. If the new container never passes its health check (because the new code has a startup bug, or the database migration failed, or a required environment variable is missing), it never receives traffic. The old version stays live.
Without a health check, the platform would just replace the old container with the new one and hope. Sometimes it works. Sometimes users get 502 errors for 10–30 seconds while the new container cold-starts.
Citation Capsule — Temps health check implementation: Temps polls the container's startup status every 5 seconds waiting for it to reach a running state, then runs HTTP health checks against a configurable path (default
/). It requires 2 consecutive successful responses before marking the container healthy. If the app returns 4xx or 5xx errors for more than 60 consecutive seconds, the deployment is failed and the old container keeps serving. The total health check timeout defaults to 300 seconds. Health check path can be overridden per-project via a.temps.yamlfile.
Once the new container passes its health check, the proxy routes new requests to it. The mechanism varies by platform:
Nginx-based platforms update an upstream block and reload the nginx config. This works but has a brief gap where in-flight requests can be interrupted.
Traefik (used by Coolify and Dokploy) supports dynamic configuration: it picks up the new container via Docker labels without restarting. In-flight requests on the old container are generally handled gracefully, though the behavior depends on Traefik's version and configuration.
Edge networks (Vercel, Cloudflare) route traffic via their global infrastructure with connection draining behavior, ensuring in-flight requests complete on the old version before it's removed.
Pingora (used by Temps) is the same proxy technology that powers Cloudflare's network — it's open-source and written in Rust. Temps uses Pingora to serve as the edge proxy, with atomic route table updates so the traffic switch happens in a single operation without any config reload pause.
The key distinction is between "stop sending new requests to old container" and "wait for old requests to finish before stopping the old container." The second is harder to implement correctly, but it's the difference between zero-downtime and almost-zero-downtime.
After traffic moves to the new container, the old container stops. Container images from old deploys get retained for a configurable period (to support rollbacks) and then pruned. Build artifacts get cleaned up.
This cleanup step is easy to neglect in a DIY setup and causes a subtle problem: if you're running frequent deploys, old Docker images accumulate and fill your disk. Platforms handle this automatically; a bare Docker setup needs a cron job running docker system prune.
Temps-specific: Temps retains the last N deployment images per project so you can one-click rollback to any prior version from the dashboard. Rolling back skips the build step entirely — the prior image is already on the node.
| Feature | Vercel | Coolify | Temps |
|---|---|---|---|
| Webhook auto-registration | Yes (GitHub/GitLab) | Yes (GitHub/GitLab/Gitea) | Yes (GitHub/GitLab/Gitea) |
| Build infrastructure | Managed cloud | Your server | Your server |
| Stale deploy cancellation | Yes | Manual | Automatic |
| Health check before traffic swap | Yes | Configurable | Yes (2 consecutive, 60s error window) |
| Proxy technology | Edge CDN | Traefik | Pingora (Cloudflare-built, open-source) |
| Atomic traffic swap | Yes | Graceful | Yes |
| One-click rollback | Yes | Yes | Yes |
| Self-hosted | No | Yes | Yes |
| Pricing | See pricing page | Free / Coolify Cloud from $5/mo | Free self-host / Temps Cloud ~$6/mo |
git push
-> webhook fires (HTTP POST to your deployment server)
-> build queued (deduplicated by SHA, stale builds cancelled)
-> repository cloned at commit SHA
-> build detection (Dockerfile or buildpacks)
-> container build (Docker, logs stream to dashboard)
-> health check (2 consecutive successes, 60s error window)
-> traffic swap (proxy re-routes requests atomically to new container)
-> old container drains and stops
-> image cleanup (prior images retained for rollback)
Every platform that does git-push deploys runs some version of this same pipeline. If you want to see what the manual version looks like without any platform, we wrote a full walkthrough: how to deploy Next.js to a VPS the manual way. For more on the traffic swap specifically, see how to add zero-downtime deployments to any Docker app.
When a deploy fails, it's almost always at one of three steps: the build (a code error), the health check (a startup bug or missing env var), or the traffic swap (a proxy misconfiguration). The health check is the safety net for all of them.
A health check configured to require only one success can still let a flaky startup through — an app that takes 3 seconds to become healthy and times out 20% of the time will pass a single-success check and fail intermittently in production. Requiring 2 consecutive successes (as Temps does) catches this class of bug before traffic ever hits the new container.
The 60-second error window is equally important. Some apps start fast but fail slow — they pass the initial connection check but start returning 500s once the database connection pool saturates. A finite error window means those failures are caught before the swap instead of discovered by your users.
Yes, with platform-specific support. Most platforms let you specify which subdirectory to build from and what the build command is. Temps supports monorepo deployments with per-project build root and command configuration. See how to deploy a monorepo with Temps for a full walkthrough.
The old deployment keeps serving traffic. The failed build is logged, you get a notification (if configured), and nothing is promoted to production. The failed build artifacts are cleaned up.
Most platforms let you disable health checks. The trade-off is that failed deployments can reach production — the platform swaps traffic to the new container even if it's not responding. Only disable health checks for deployments where you have another safety mechanism (e.g., a separate smoke test step in your CI pipeline that runs before pushing).
git push deployment different from CI/CD?They're not mutually exclusive. In most teams, CI runs tests on every push, and if tests pass, the CI pipeline triggers a deployment. The git push deployment platform handles the "deploy" half: cloning, building, and running the container. The CI platform handles tests, linting, and the decision of whether to promote. Vercel and Temps both integrate with GitHub Actions so you can combine both.
Related: Read the full v0.1.0 release notes · How to add zero-downtime deployments to any Docker app