---
title: Contributing to Docker images
description: How to build and extend Tale's Docker images for forks, vendored builds, or air-gapped distributions.
---

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](/self-hosted/operate/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.

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

- **Caddyfile** — `services/proxy/Caddyfile` controls routing and TLS termination. Custom headers, custom subdomains, and custom rate limits land here.
- **Platform plop templates** — `services/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 extractors** — `services/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 allowlist** — `services/sandbox-egress/tinyproxy.conf` is what the runtime allowlist compiles into. The runtime path goes through the [run-code policy](/platform/admin/governance/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](/self-hosted/operate/container-architecture); the upgrade workflow that runs the published images is in [Upgrades](/self-hosted/operate/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.
