How Deploys Work
Most deploy problems are easier to fix once you know which step they happened in. This page walks through what ZevCloud does between your push and your app answering requests.
The pipeline
Section titled “The pipeline”push ──▶ match ──▶ clone ──▶ detect ──▶ build ──▶ start ──▶ route1. Match. A push to your connected repository tells us which services to deploy. Only services on that repository and that branch are considered. If several services share one repository, we also compare the changed files against each service’s Root Directory, so a commit that only touches frontend/ doesn’t rebuild your API. Changes to shared files outside every service folder rebuild all of them.
2. Clone. We fetch that exact commit. If an image for this service already exists for the same commit, we skip straight to the start step — this is why redeploying an unchanged commit is near-instant.
3. Detect. We read your Root Directory and work out how to build it:
- A
Dockerfilethere means you’ve already described your build, so we use it as-is. See Using a Dockerfile. - Otherwise we look for a manifest —
package.json,requirements.txt,pyproject.toml,go.mod,Gemfile,composer.json— and hand the folder to Nixpacks, which works out the language, the system packages it needs, and how to install and start it. Its provider docs list exactly which files each language is detected by, which is the quickest way to check why your project was detected as one thing rather than another.
You can see the resolved plan at the top of any build log, including the detected language and the exact install and start commands.
4. Build. The plan runs in three phases:
| Phase | What happens | Example |
|---|---|---|
| Setup | System packages your stack needs | Python, Node, a compiler when a dependency requires one |
| Install | Your dependencies | npm ci, pip install -r requirements.txt |
| Build | Your build command, if you have one | npm run build |
5. Start. The new container runs your start command and has to answer on the configured Port before it receives traffic.
6. Route. Traffic moves to the new container and the previous one stops. Your domain and certificate carry over untouched.
What gets cached
Section titled “What gets cached”Each phase is a layer, and layers are reused until something they depend on changes. Your source code is copied in last, so a code-only change reuses everything above it.
| Layer | Rebuilt when |
|---|---|
| System packages | Your detected stack changes — a new dependency needs a compiler, you change runtime version |
| Dependencies | Your manifest or lockfile changes |
| Your code | Every commit (this is the cheap part) |
The practical shape of this: the first build of a project is the slow one. A first build that takes several minutes is normal, and later builds of the same project are usually well under a minute. A build that’s slow every time means something is invalidating a layer on each run, and that’s worth looking at.
Why a build is slow
Section titled “Why a build is slow”Work through these in order.
Is it only the first build?
Section titled “Is it only the first build?”Compare durations in the Deployments tab. First build slow, subsequent builds fast is the system working correctly — there’s nothing to fix.
Does a dependency compile from source?
Section titled “Does a dependency compile from source?”This is the most common cause of a build that’s slow every time. Some packages ship only source code, so your build has to install a compiler and build them on every machine that hasn’t done it before. Swapping to a prebuilt equivalent is usually a one-line change:
| Slow | Fast | Language |
|---|---|---|
psycopg2 | psycopg2-binary | Python |
mysqlclient | pymysql | Python |
bcrypt | bcryptjs | Node |
node-sass | sass | Node |
sharp pinned to an old version | current sharp | Node |
psycopg2 is worth calling out: it pulls in a C compiler and PostgreSQL development headers before it can build, which adds several minutes on its own. psycopg2-binary is a drop-in replacement that installs in seconds, and both SQLAlchemy and Django accept it without any code change.
When you create a service, ZevCloud flags these in your manifest before you deploy.
Is your lockfile changing on every commit?
Section titled “Is your lockfile changing on every commit?”If your lockfile is regenerated or reformatted on each commit, the dependency layer can never be reused. Commit a stable lockfile and leave it alone between dependency changes.
Are you installing things you don’t need at runtime?
Section titled “Are you installing things you don’t need at runtime?”Browser automation tools (Puppeteer, Playwright, Cypress) download 100–200 MB of browsers on install. If they’re in devDependencies and your build doesn’t run tests, they’re pure cost. ZevCloud sets variables to skip those downloads by default on Node projects.
Is the build doing work your app doesn’t need?
Section titled “Is the build doing work your app doesn’t need?”Machine-learning frameworks (torch, tensorflow) are hundreds of megabytes. That’s a real cost you may need to pay, but it’s worth checking it’s actually imported.
Reading a failed build
Section titled “Reading a failed build”Open Deployments → the failed deploy. Match the last thing in the log to a phase above:
| Last thing you see | Phase | Usually means |
|---|---|---|
failed to detect the application type | Detect | Root Directory is wrong, or the folder has no recognised manifest |
A package manager error, ERESOLVE, a compiler error | Install | A dependency problem — read the first error, not the last |
| Your own build command failing | Build | Reproduce locally with the same command |
| Build succeeded, deploy failed on health checks | Start | Your app isn’t answering on the configured Port |
| Deploy succeeded, every URL returns Bad Gateway | Route | The Port setting doesn’t match what your app listens on |
The last two are the same root cause and are covered in detail under Troubleshooting.
Things that surprise people
Section titled “Things that surprise people”Environment variables apply at build, not at restart. A variable change reaches your app on the next deploy. Restarting a running container won’t pick it up. The dashboard reminds you while you have unapplied changes.
A restart is not a redeploy. Restart reuses the existing image. Settings that affect how a container is built or routed — Port, build commands, variables — need a redeploy.
Build timeouts scale with your plan. A build that’s cut off mid-way is a timeout, not a crash. Limits are listed under Plans.
The Root Directory is also the auto-deploy filter. Setting it to a subfolder means pushes that don’t touch that folder won’t deploy. That’s usually what you want, and occasionally surprising when you expect a deploy and don’t get one.