Skip to content

Temporal Retention & History Limits ​

Approval gates can park a run for days. The graph engine parks on an unbounded wait_condition, so a parked run adds O(1) history events per approval decision — far below Temporal's per-execution history limits. Two deployment-side settings back this up:

  • Namespace retention: 168h (7 days). How long closed workflow histories stay queryable (tctl, handle.describe(), run status reconciliation for recently finished runs). The Temporal console is closed by default and opened on demand — see Temporal Admin Access.
  • Raised history limits. Warn at 51,200 events / 25 MiB and error at 262,144 events / 100 MiB, so a slow event leak degrades into server-log warnings instead of killing multi-day runs.

New deployments ​

Nothing to do. Both compose files set on the temporal service:

yaml
environment:
  - DEFAULT_NAMESPACE_RETENTION=168h
  - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/custom.yaml
volumes:
  - ./temporal/dynamicconfig.yaml:/etc/temporal/config/dynamicconfig/custom.yaml:ro

temporalio/auto-setup passes DEFAULT_NAMESPACE_RETENTION to temporal operator namespace create when it creates the default namespace, and the server loads the mounted dynamic config at boot.

Changing retention on a running deployment ​

DEFAULT_NAMESPACE_RETENTION only applies on first boot — auto-setup skips namespace creation when the default namespace already exists, so recreating the container does not change retention. Update it in place:

bash
docker exec <temporal-container> sh -c 'tctl --address $(hostname -i):7233 --ns default namespace update --retention 168h'

The explicit --address matters: the compose files leave BIND_ON_IP unset, so the image's entrypoint binds the frontend to the container's network IP — not 127.0.0.1, which is where a bare tctl connects (this is also why the compose healthcheck passes --address temporal:7233). A bare tctl inside the container will fail with a connection error.

The dynamic-config half needs no manual step: recreate the service (docker compose up -d temporal) so the volume mount and DYNAMIC_CONFIG_FILE_PATH take effect. Temporal also hot-reloads the file while running (~60s poll), so later edits to deploy/compose/temporal/dynamicconfig.yaml apply without a restart.

Verifying ​

Retention shows 168h0m0s:

bash
docker exec <temporal-container> sh -c 'tctl --address $(hostname -i):7233 --ns default namespace describe'

Dynamic config loaded — the server logs the file at startup:

bash
docker logs <temporal-container> 2>&1 | grep -i "dynamic config"

The keys in deploy/compose/temporal/dynamicconfig.yaml (limit.historyCount.warn/error, limit.historySize.warn/error) are verified against Temporal server v1.24.2 (common/dynamicconfig/constants.go); a typo'd key is silently ignored, so check the file against those constants when bumping the server image.

What these limits do NOT fix ​

The limits are defense in depth, not a license to generate history. A workflow that legitimately approaches the warn thresholds (very large graphs, thousands of activities) is a candidate for continue-as-new — the worker logs a warning once per run when Temporal suggests it (is_continue_as_new_suggested). Flow runs do not roll over automatically, so a run approaching the error threshold should be split into smaller flows.

Released as open source under the AGPL-3.0-or-later license. Development is sponsored by Rexonix s.r.o.. Contact — [email protected].