Paired bars of seconds from code to first response for a Java app. Zip: 198 on the first deploy, 95 after a one-line change. Git, full clone: 201 on the first deploy, 96 after a one-line change. Git, shallow clone: 197 on the first deploy, 97 after a one-line change. Container image: 200 on the first deploy, 94 after a one-line change.

There are three common ways to hand a host your code: upload a zip, point it at a git URL, or push a container image. We deployed the same two open-source apps each way, on a fresh host every time, and timed each step until the first 200 response. We ran 48 deploys in the main test, and the choice of path changed the total by much less than we expected.

Short version: on the Java app the build took 191 s of a 198 s first deploy, about 97%, and every path needed that build somewhere. Moving the code took under 5 s here. What does differ is where the build runs and how many bytes travel: a zip of the Java app was 529 KB, its image 175 MB. On a repo with a long history, a shallow clone also matters: Django took about 95 s to clone in full and 6 s shallow.

What we deployed

AppRepoCommit testedDockerfile
Python[heroku/python-getting-started](https://github.com/heroku/python-getting-started)561bc55, 10 Oct 2026Python 3.15 slim, pip install, collectstatic, gunicorn
Java[spring-projects/spring-petclinic](https://github.com/spring-projects/spring-petclinic)500158f, 29 Sep 2026Maven build stage, then a Temurin 21 JRE with the jar

Each path ran three rounds, and each round started from a new Docker host with an empty cache, so a first deploy also downloads the base images and every dependency. The second deploy in each round adds one comment line to a source file and goes through the same path again. Tables show medians.

PathWhat happensWhere it builds
Zip`zip -r`, copy to the host, unzip, `docker build`On the host
Git, full clone`git clone --branch main`, `docker build`On the host
Git, shallow clone`git clone --depth 1 --single-branch --branch main`, `docker build`On the host
Container image`docker build` on a second machine, `docker push` to a registry, `docker pull` on the hostOn the second machine

The hosts were Docker 29 containers on one Windows PC running Docker Desktop on WSL2, with 6 CPUs and 11.7 GB. Git clones came from GitHub over our real connection. The zip copy and the image push and pull only crossed a local Docker network, because we had no second site to send them from. So the transfer times for those two are a best case, and we work out real link times from their sizes further down.

First deploy

App and pathMove the codeBuildStart to first 200Total
Java: zip0.17 s191 s6.4 s198 s
Java: git, full clone4.4 s191 s6.2 s201 s
Java: git, shallow clone1.2 s190 s6.2 s197 s
Java: container image3.4 s190 s6.6 s200 s
Python: zip0.08 s27 s0.58 s28 s
Python: git, full clone1.2 s26 s0.56 s28 s
Python: git, shallow clone1.1 s30 s0.58 s32 s
Python: container image2.6 s26 s0.56 s30 s

"Move the code" adds up the steps before the build: pack, copy and unzip for a zip, the clone for git, and push plus pull for an image. The pull includes unpacking layers, which is probably why it takes longer than the push here. Start to first 200 runs from docker run until the app answers, and it depends on the runtime, not on the path. We looked at that separately in Cold starts measured.

Across the four paths the Java totals only ran from 197 to 201 s and the Python totals from 28 to 32 s. The rounds of a single path varied by several seconds, so we wouldn't rank the paths on those gaps.

After a one-line change

App and pathMove the codeBuildStart to first 200Total
Java: zip0.19 s88 s6.6 s95 s
Java: git, full clone4.2 s85 s6.9 s96 s
Java: git, shallow clone1.4 s89 s6.4 s97 s
Java: container image0.30 s86 s7.0 s94 s
Python: zip0.10 s1.5 s0.46 s2.0 s
Python: git, full clone1.2 s1.5 s0.42 s3.0 s
Python: git, shallow clone1.1 s1.5 s0.44 s3.0 s
Python: container image0.44 s1.7 s0.47 s2.5 s

The Python app drops to about 3 s because its install layer stays cached and only the last steps rerun. The Java app still takes about 96 s: this Dockerfile copies everything before mvn package, so any change makes Maven download its dependencies and build again, on every path. That's a Dockerfile matter, not a deploy path matter. We measured the fix in Rebuild time when only your code changes.

One caveat on these rebuilds: a host that keeps its build cache gets them. A host that starts clean for each deploy pays the first-deploy build every time.

Bytes on the wire

This is where the paths really do differ. The sizes below are measured. The times are simple arithmetic at 100 Mbit/s (12.5 MB/s), not measurements, with no allowance for protocol overhead.

What travelsPythonAt 100 Mbit/sJavaAt 100 Mbit/s
Zip of the source26 KBunder 0.1 s529 KBunder 0.1 s
.git after a shallow clone100 KBunder 0.1 s580 KBunder 0.1 s
.git after a full clone355 KBunder 0.1 s11.7 MB0.94 s
Image, first push (compressed)57.8 MB4.6 s175 MB14 s
Image, new layers after a change400 KBunder 0.1 s59.5 MB4.8 s

The last Java figure took an extra run. In the main test the Java image came out unchanged after the edit: a comment doesn't alter the compiled code, and the registry received 0 new bytes. That would flatter the image path, so we also deployed a change that reaches the jar, one extra line in a properties file. That pushed 59.5 MB of the 175 MB image, because the Spring Boot jar is a single layer, dependencies included. It was one run, but the size is the point there. Spring Boot's own docs call one layer not optimal and show how to split the dependencies out.

An image also travels twice: once from the build machine to the registry, and once from the registry to the host. A zip of source goes one way, but it's small only if it's clean. Ours came from git archive. A zip of a working folder with dependencies is another matter, as we measured in What a missing .dockerignore costs.

Full or shallow clone

On our two test apps the gap is small, about 3.2 s on Java. Their histories are short. So we also cloned django/django, which has 35,081 commits on main, three times each way:

Clone of django/django.git sizeTime, whole seconds
Full310 MB95 s (95, 88, 101)
`--depth 1 --single-branch`13.5 MB6 s (6, 6, 6)

A deploy only needs the files of one commit, so the history is overhead unless the build asks for it. Git's clone documentation notes that --depth already implies --single-branch; we pass both to make the intent plain. Check your build first: anything that runs git describe or counts commits for a version number sees only one commit of history.

bash
# all a deploy needs
git clone --depth 1 --single-branch --branch main https://github.com/you/app /deploy

Where the build runs

The totals hide what may be the bigger difference. With a zip or git, the host does the build: the Java app used it for about 191 s, downloading dependencies and compiling. With an image, the host only pulled and started, about 9.5 s here on a local network. The build still took 190 s, only on another machine.

Zip or git URLContainer image
You needSource and a Dockerfile, or a platform that detects the runtimeA build machine or CI, and a registry
Build runs onThe host or its builderYour machine or CI
What ran is pinned toA commit, or a zip you keep; a rebuild can pick up newer dependencies unless they're lockedAn image digest, the same bytes on every host
Same version on many hostsEach one builds, unless a shared builder is usedEach one pulls

Our reading, as opinion rather than measurement: for one app on one host, pick whichever is easiest, because the clock won't decide it. Use a shallow clone if you deploy from git, and keep zips clean. An image starts to earn its extra machinery when several hosts run the same version, when you want to roll back to the exact bytes you ran, or when the host is too small to run your build.

What this test doesn't show

It's one machine, where the deploy hosts and the build machine shared 6 CPUs, so a larger builder would shorten the builds and a remote host would lengthen the zip and image transfers. One Java deploy failed on a Docker Hub timeout, so we discarded that pair and repeated it; the failure stays in our raw log and out of the medians. We didn't test private repositories, automatic deploys on push, a registry-backed build cache or the time to switch traffic, all of which change the picture. The apps are small samples of their runtimes: a Python app with heavier dependencies would spend longer in its first build.

Sources (read 3 Nov 2026): git clone documentation and Spring Boot's efficient container images page. All timings and sizes are our own, from the setup above.

On systemi you send a zip of up to 50 MB or an https git URL and a branch. We fetch only the latest commit, then build and start your app and switch traffic over when it answers.

See how deploys work