Contributing to Docker images
How to build and extend Tale's Docker images for forks, vendored builds, or air-gapped distributions.
3 min read
Every container Tale ships has its Dockerfile in the public source repo. Forks, air-gapped distributions, and one-off patches all start from the same files; this page is the operator's walk through building the images yourself, where the customisation seams live, and how to keep a fork in sync with upstream without diverging on the boring parts.
The container architecture lives at Container architecture; this page is what you read when the published images do not fit and you need to build your own.
What the images are
Eight images, one Dockerfile each, all under services/<name>/:
| Image | Source path | Base |
|---|---|---|
tale-proxy | services/proxy/ | Caddy |
tale-platform | services/platform/ | Bun + Debian slim |
tale-convex | services/convex/ | Convex local-backend |
tale-db | services/db/ | ParadeDB (Postgres) |
tale-rag | services/rag/ | Python + FastAPI |
tale-crawler | services/crawler/ | Crawl4AI |
tale-sandbox | services/sandbox/ | Bun + Docker CLI |
tale-sandbox-egress | services/sandbox-egress/ | Alpine + tinyproxy |
The two compose files at the repo root (compose.yml for development, the CLI-generated production compose) reference these by ghcr.io/tale-project/tale/<image>:<tag>. A local build replaces the registry pull with a build: block in compose.
Building locally
A first build of every image takes about 15 minutes on a recent laptop; subsequent builds hit Docker's layer cache and finish in under a minute for the image you changed.
# Build every image in compose.yml
docker compose build
# Build one image
docker compose build platformSet PULL_POLICY=build in your environment (or in .env) to force compose to build rather than pull the published image. The shipped compose.yml defaults to build, so a local clone with no overrides already builds; production compose files generated by tale deploy default to always and pull from the registry.
The customisation seams
The supported extension points for forks are at the Dockerfile level. The image's entrypoint and the configuration files inside it are stable — patch them, build the image, and the rest of the system does not need to know.
- Caddyfile —
services/proxy/Caddyfilecontrols routing and TLS termination. Custom headers, custom subdomains, and custom rate limits land here. - Platform plop templates —
services/platform/Dockerfileruns a build step that bakes in the messages, the schema, and the static assets. A fork that ships custom UI strings or extra routes builds the platform image. - RAG document extractors —
services/rag/app/routers/documents.pyis where new file formats register; a fork that needs an extractor for a proprietary file type patches here. - Sandbox egress allowlist —
services/sandbox-egress/tinyproxy.confis what the runtime allowlist compiles into. The runtime path goes through the run-code policy screen; the build-time default lives here.
What is not a supported seam: the platform container's runtime code (services/platform/app/) — those files are application code, not configuration. Patches there are a real fork and ride the upgrade tax.
Tagging and pushing your own registry
For air-gapped or vendored distributions, the path is "build, tag, push to your registry, change the compose image: lines."
# Build, tag, push
export REGISTRY=registry.internal.example.com/tale
docker compose build
docker tag ghcr.io/tale-project/tale/tale-platform:latest \
$REGISTRY/tale-platform:vendored-1.0
docker push $REGISTRY/tale-platform:vendored-1.0The CLI's deploy generates a compose file with the registry path; either patch the generated file post-generation, or skip the CLI and run docker compose directly against a compose file you maintain yourself.
Staying in sync with upstream
The cheap path is a fork on GitHub that periodically merges from tale-project/tale@main. Conflicts land in the files you patched; the rest carries through clean. The two anti-patterns:
- Patching application code instead of contributing it back. If the change is broadly useful, upstream a PR — every release tax goes down.
- Pinning to an old base image. The Caddy, Bun, and Postgres bases pick up security patches on rebuild; pinning the base for "stability" is borrowing trouble.
Where this fits
This page is the contributor-facing seam of the operator story. The architecture overview lives in Container architecture; the upgrade workflow that runs the published images is in Upgrades. If your fork is non-trivial, the conversation worth starting before you write code is the one on the project's Discord or GitHub Discussions — many forks end up being features waiting to land upstream.