Bug Report: VCS deployment sourceSize reports the whole cloned repo (.git included), inflating totalSize and deployments-storage metrics
Reproduction steps
- Self-hosted Appwrite
2.0.0, PostgreSQL. Any Git-connected function whoseproviderRootDirectoryis a subfolder of a repository (a monorepo makes this obvious, but any repo shows it). - Push a commit and let the VCS build run.
- Look at the deployment in the Console, or
GET /v1/functions/{id}/deployments.
Observed on a repo with 19 Git-connected functions, each with its own providerRootDirectory:
function root dir sourceSize buildSize
contact-email appwrite-functions/contact-email 4.33 MB 2.49 MB
document-parser appwrite-functions/document-parser 4.33 MB 2.43 MB
motive-sync appwrite-functions/motive-sync 4.33 MB 2.94 MB
ifta-report appwrite-functions/ifta-report 4.33 MB 3.01 MB
arrival-nudger appwrite-functions/arrival-nudger 4.33 MB 2.89 MB
… all 19 … 4.33 MB (varies)
sourceSize is byte-identical across every function, regardless of root directory or how much code the function contains. It equals the size of the whole repository: git ls-files -z | xargs -0 tar czf - on the same commit is 4.46 MB.
The same function deployed manually (a tarball of just that folder) reports sourceSize 0.07 MB, and produces a functionally identical build — 2.86 MB vs 2.89 MB.
Expected behavior
sourceSize should describe that deployment's source — the content under providerRootDirectory that was actually built — as it did in 1.9.x, and as the manual-upload path still does.
Actual behavior
For VCS deployments, sourceSize is the size of the temporary clone directory, which is dominated by .git. git fetch --depth=1 downloads every blob of the commit's tree into the object store; core.sparseCheckout only limits which files are written to the working tree. So the number reflects the repository, not the deployment, and is the same for every function built from that repo.
Two consequences:
1. The Console's total is inflated. totalSize = sourceSize + buildSize, so a deployment showing 7.22 MB is really a 2.89 MB artifact plus 4.33 MB of a clone that was deleted seconds later. Users reasonably read this as their function having doubled in size. It also makes the Git and manual paths incomparable, which is what led us here.
2. It is counted as stored bytes when nothing was stored. sourcePath is NULL for every VCS deployment — no source archive is kept. Yet StatsResources.php reports:
['metric' => METRIC_DEPLOYMENTS_STORAGE, 'value' => (int) $dbForProject->sum('deployments', 'sourceSize'), …]
On this instance that is 525 MB of "deployments storage" that does not exist on disk (127 VCS deployments × ~4.2 MB), against 1.9 MB genuinely stored by the 21 manual deployments. On Cloud, this metric would over-report — and if it informs plan limits or billing, over-charge — by roughly the repository size on every Git build.
Builds themselves are correct: sparse checkout works, the right folder is built, and the artifact is right. This is purely the reported size.
This is a 1.9 → 2.0 regression, with before/after from the same instance
Our previous host still had its pre-upgrade database. Same repository, same functions, same providerRootDirectory values:
| period | version | VCS sourceSize |
source stored (sourcePath set) |
|---|---|---|---|
| Aug 2026 | 1.9.x | 0.09 MB avg (min 0.01, max 0.52) | 19 of 21 |
| Sep 2026 | 2.0.0-rc.2 | 4.19 MB | 0 of 1 |
Per-function on 1.9: dashboard_read 0.08 MB, invoice_admin 0.08 MB, ifta_report 0.10 MB — all Git builds, all in the same range as a manual tarball (0.06 MB).
So 1.9 packaged the sparse checkout as a source archive, stored it, and reported that. 2.0 stopped storing a source archive for VCS builds and began reporting the raw clone instead.
Where it comes from
src/Appwrite/Platform/Modules/Functions/Workers/Jobs.php — sourceSize arrives as a reported artifact and is written straight through:
// A stat artifact reports the file's byte size as its 'content'.
$size = (int) ($data['content'] ?? 0);
…
'sourceSize' => $size,
'totalSize' => $size + (int) $deployment->getAttribute('buildSize', 0),
The clone that gets measured is built by generateCloneCommand in utopia-php/vcs (GitHub.php), which sets core.sparseCheckout and then git pull --depth=1 / git fetch --depth=1 — worktree limited, object store not.
Suggested fixes, cheapest first:
- Exclude
.gitwhen measuring (the samersync -av --exclude '.git'the template path inBuilds.phpalready uses), so the number reflects the checked-out source. - Or measure only
providerRootDirectory. - Or, if nothing is stored for VCS builds, report
sourceSizeas0and keep it out ofMETRIC_DEPLOYMENTS_STORAGE, so the storage metric counts only bytes that exist.
Your Environment
- Appwrite 2.0.0 self-hosted (Docker Compose), PostgreSQL 18.3 (
appwrite/postgres:0.1.0) - Executor
openruntimes/executor:0.29.0, orchestratorghcr.io/open-runtimes/orchestrator:1.9.2 - Go functions (
go-1.26), monorepo, oneproviderRootDirectoryper function - Previous host for the 1.9 comparison: same repo, Appwrite 1.9.x on MariaDB 10.11
Additional information
Happy to supply the full per-deployment table, or to test a patch. Related report from the same instance: #13485.
Source: appwrite/appwrite