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.

4 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

The stack is entirely TypeScript — no Python image. Each image has one Dockerfile 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-sandboxservices/sandbox/Bun + Docker CLI
tale-sandbox-egressservices/sandbox-egress/Alpine + tinyproxy
tale-sandbox-runtimeservices/sandbox-runtime/Bun + Chromium + Playwright
tale-sandbox-buildkitdservices/sandbox-buildkitd/Debian + BuildKit + redsocks
tale-controllerservices/controller/Bun + Docker CLI

Both database containers — db and knowledge-db — build from the same tale-db ParadeDB image; the difference is the database each one serves. The LLM gateway, tale-sandbox-llm-gateway, is a pinned upstream image (maximhq/bifrost), so it has no Dockerfile in the repo. The 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.
  • Sandbox runtime imageservices/sandbox-runtime/Dockerfile is the execution environment for Run code, web rendering, and document generation; it already carries Chromium and Playwright. A fork that needs an extra system package or a different browser build patches here.
  • Sandbox egress proxyservices/sandbox-egress/tinyproxy.conf.template is the proxy config the entrypoint renders at startup: open egress by default, or a default-deny hostname filter when SANDBOX_EGRESS_ALLOWLIST is set. A fork that needs different proxy behaviour patches here.

What is not a supported seam: the convex backend's application code, including document extraction and the RAG and crawler logic that now live in-process (services/platform/convex/), and the platform container's runtime code (services/platform/app/). Those files are application code, not configuration — adding a document-format extractor or changing retrieval behaviour is a real fork and rides 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.