Skip to content

Rendered config templates aren't part of the atomic release #126

Description

@ineedjet

Problem

deploy/deploy.py renders each app's config/*.template.* files on the CI runner (render_app_configs), but the rendered output isn't written into the release directory - it's pushed straight into the target host's persistent, non-versioned apps-data/{app}/config/ tree, in place:

```python
def deploy_to_host(host, archive_path, rendered_configs, apps, networks, config):
...
push_release(connection, archive_path, release_path) # release lands in releases/{timestamp}/
push_app_configs(connection, base_path, rendered_configs) # <- overwrites apps-data/{app}/config/*, NOT part of the release tar
...
connection.run(f"ln -sfn {shlex.quote(release_path)} {shlex.quote(current_path)}", hide=True) # atomic switch
```

This runs before the current symlink switch and mutates a directory that isn't tied to any specific release. Consequences:

  • It breaks the atomicity guarantee AGENTS.md documents elsewhere ("a deploy either fully lands and switches current as its last step, or fails leaving current untouched - never a partially-applied app"): if the run dies between push_app_configs and the symlink switch, apps-data now holds config for a release that never went live.
  • Rendered config has no history. Each deploy overwrites the previous render in place, so there's nothing to fall back to even for apps whose .env (correctly stored inside the release tar) IS versioned per release.
  • It only works today because the affected apps' compose files hardcode the apps-data mount path (e.g. apps/traefik/docker-compose.yml: ../../apps-data/${APP_NAME}/config/traefik.yml:/traefik.yml:ro) - a convention inherited unreflectively from the pre-Fabric up.sh model, where apps-data was always the render target because there was no versioned release tree to put it in.

Fix

Drop the config/ subdirectory entirely and rely on naming alone: any *.template.* file sitting directly next to an app's docker-compose.yml is a template. render_app_configs renders it in place, into a sibling file in the same directory (traefik.template.yml -> traefik.yml), on the runner, before the release is archived - so the rendered file travels inside the release tarball automatically, right alongside .env, with no separate push step and no special nesting needed.

Compose files mount the rendered file directly by its plain relative path, one line per file (./traefik.yml:/traefik.yml:ro) rather than mounting a whole directory - more explicit about which container path each file lands at. apps-data/{app}/ stays reserved strictly for state that must genuinely persist across releases and is never regenerated by a deploy (acme.json, database data directories, codecov's archive/) - never templated config.

push_app_configs and the rendered_configs dict threaded through resolve_app_envs/deploy_to_host/main go away entirely as part of this.

Related

Also flagged while reviewing this: AGENTS.md had no dedicated section explaining the templating mechanism itself - only scattered one-line mentions across several sections, one of which (the Field Ordering Rules example) didn't even match how any real app in the catalog did it. Added a proper "Config Templates" section covering the naming convention, rationale, and when to prefer a plain environment: var over a template.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions