Skip to main content

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>/:

ImageSource pathBase
tale-proxyservices/proxy/Caddy
tale-platformservices/platform/Bun + Debian slim
tale-convexservices/convex/Convex local-backend
tale-dbservices/db/ParadeDB (Postgres)
tale-ragservices/rag/Python + FastAPI
tale-crawlerservices/crawler/Crawl4AI
tale-sandboxservices/sandbox/Bun + Docker CLI
tale-sandbox-egressservices/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.

bash
# Build every image in compose.yml
docker compose build

# Build one image
docker compose build platform

Set 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.

  • Caddyfileservices/proxy/Caddyfile controls routing and TLS termination. Custom headers, custom subdomains, and custom rate limits land here.
  • Platform plop templatesservices/platform/Dockerfile runs 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 extractorsservices/rag/app/routers/documents.py is where new file formats register; a fork that needs an extractor for a proprietary file type patches here.
  • Sandbox egress allowlistservices/sandbox-egress/tinyproxy.conf is 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."

bash
# 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.0

The 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.

© 2026 Tale by Ruler GmbH — ISO 27001 & SOC 2 certified.

Tale is MIT licensed — free to use, modify, and distribute.