Dependency discipline for FastAPI + Svelte + Docker projects
Architecture & Engineering · 2026
Setups like ours — FastAPI on the backend, Svelte on the front, everything containerised in Docker — feel modern and productive right up until the day a dependency quietly breaks your build. That moment is when ugly words are uttered in abundance. The details that actually matter aren't the headline features, they're the disciplines that stop your moving parts from moving on you.
Quick decision matrix
Tldr; Before diving in, match your situation to the right tooling:
Scenario | Python manager | JS manager | Notes |
New project, speed matters | uv | pnpm | Best Docker cache behaviour |
Library publishing to PyPI | Poetry | pnpm | Better PyPI tooling |
Existing codebase | Keep what's there | — | Migration cost rarely worth it |
Go rewrite candidate | n/a | pnpm | API surface thin + mostly static content |
1. Lockfiles are your foundation
Lockfiles are the single most valuable habit you can adopt early. They turn a fuzzy 'install the latest compatible version' into a deterministic, reproducible build. The key distinction:
A manifest (pyproject.toml, package.json) declares what you want
A lockfile (uv.lock, pnpm-lock.yaml) records exactly what you got — every transitive dependency pinned to a precise version
Common misconception: Lockfiles do NOT pin to commit hashes. They pin to exact version numbers. Hashes only appear for direct git/VCS dependencies, which is rare in production services.
Python: uv (recommended for new projects)
The Python packaging landscape changed substantially in 2024–2025. uv, which is written in Rust by the team behind the Ruff linter, is now the strongest default for new projects. It replaces pip, virtualenv, pyenv, and pip-tools with a single fast binary, and generates a uv.lock file that pins every transitive dependency. The trousers suddenly feel tight.
Key advantages for containerised workloads:
10–100× faster installs than pip or Poetry
Single binary with no runtime Python dependency — copies cleanly into Docker images
Manages Python version itself (replaces pyenv)
Standards-compliant pyproject.toml output
Poetry note: Poetry remains a strong choice for library publishing to PyPI and for teams with established Poetry workflows. Poetry 2.0 (Jan 2025) added full PEP 621 support. The migration cost from Poetry to uv rarely justifies itself on existing codebases.
JavaScript: pnpm
On the Svelte side, pnpm with its pnpm-lock.yaml is the right choice — faster installs than npm, less disk usage via content-addressable storage, and excellent Docker cache behaviour. package.json is your manifest; the lockfile is law.
2. Docker layer ordering: The one pattern that matters most
The most common and expensive mistake in containerised Python/Node projects is copying application code before installing dependencies. This busts the Docker layer cache on every code change and forces a full reinstall.
❌ The anti-pattern (reinstalls on every code change):
COPY . .
RUN pip install -r requirements.txt
✅ Correct pattern (cache preserved when deps unchanged):
# Copy lockfile + manifest FIRST
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
# Then copy application code
COPY . .
Multi-stage builds: slim runtime images
Use two stages: a fat builder that installs dev dependencies and runs tests, and a slim runtime that only ships what's needed in production.
FROM python:3.12-slim AS builder
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
# --- Runtime stage ---
FROM python:3.12-slim
COPY --from=builder /app/.venv /app/.venv
COPY . .
CMD [".venv/bin/uvicorn", "main:app"]
The same principle applies on the Svelte side: copy package.json and pnpm-lock.yaml before running pnpm install, and build the static bundle in a separate stage.
3. Automate the boring parts
Manual dependency hygiene doesn't survive contact with a real team. Wire these up once and let them run:
Automated updates
Tools like Dependabot or Renovate open PRs when patches land. Renovate is typically the better choice for mixed-language monorepos since it handles Python, JavaScript, and Docker images in a single config. Let them do the boring work so you stay current on security patches without thinking about it weekly.
Container image scanning
Scan images locally with Trivy before they hit CI! It catches supply-chain surprises early:
trivy image myapp:latest
SBOM generation (enterprise / compliance contexts)
For teams with compliance requirements, also generate a Software Bill of Materials. Syft and GitHub's built-in SBOM export cover this. Trivy alone is not sufficient for a full enterprise architecture review:
syft myapp:latest -o spdx-json > sbom.json
Separate dev from production dependencies
Keep test frameworks (pytest, Playwright), linters, and the Svelte dev server out of production images. In uv this is the --no-dev flag on sync. In Poetry it's --without dev. In pnpm it's --prod.
4. When to consider go instead of FastAPI
This is a genuine architectural choice, not a rewrite impulse. The right question is: what does your service actually do?
Go makes sense when:
You're serving mostly static content with a thin API layer (comments, search, admin)
Binary startup time and container size are business constraints (serverless, edge deployments)
Your team already knows Go
Go compiles to a single binary. The Go module system (go.mod + go.sum) is reproducible by design. Once go.sum is committed, the build is locked. Realistic production containers typically land in the 15–40 MB range depending on your dependencies and base image choice (scratch, distroless, alpine).
Container size note: A "hello world" Go binary on scratch can be under 10 MB. A realistic service with HTTP routing, middleware, and a database driver is typically 15–40 MB. That is still dramatically smaller than a Python runtime, but worth scoping honestly.
FastAPI is the right call when:
You're using Pydantic for validation, fastapi-users for auth, or any of Python's rich data/ML ecosystem
Async route performance is already sufficient for your load
Your team's Python expertise is higher than their Go expertise
FastAPI is one of the lightest and most productive backends available. There's no urgent reason to leave it, but, the discipline around lockfiles and Docker layers, not the runtime choice, is what prevents the "build broke on Tuesday" experience.
5. Why this feels harder than PHP or .NET
A brief note for those who remember the older world: composer.lock (PHP) and packages.lock.json (.NET) solved the same reproducibility problem. The difference today isn't really tooling quality but rather a dependency graph depth.
.NET's lock file is opt-in, not on by default, and both ecosystems benefited from heavier, more opinionated runtimes that kept transitive dependency trees shallow. Modern Python and JavaScript give you lighter runtimes and async superpowers, but with shallower batteries-included philosophy comes a larger graph of third-party packages. Each of which can change its API or introduce a vulnerability seemingly overnight. Because how can anyone keep up?
The lockfile discipline is what keeps the new stack from feeling chaotic. The tools are better now; the surface area is just larger.
Summary
The things that actually survive the next five years aren't the technology choices, but the boring process habits around them:
Use uv for new Python projects; keep Poetry if you're publishing libraries or already on it
Use pnpm + pnpm-lock.yaml for JavaScript
Copy lockfiles before code in your Dockerfile. Like always.
Multi-stage builds: one stage that installs and tests, one that ships
Automate updates (Renovate) and scans (Trivy, syft for compliance)
Consider Go only if container size or startup time are genuine constraints
Key principle: Commit your lockfiles. Treat them as sacred contracts between your local machine, CI, and production. That single habit eliminates the majority of 'works on my machine' incidents.