v3.1.0 d676137

migrate-to-v3.md RawHistoryFolder

Migrate to Gordon v3

Gordon v3 replaces domain-based route workloads with declarative apps. The upgrade is intentionally explicit: Gordon does not infer app ownership from domains, does not adopt existing volumes, and does not copy domain secrets into app secrets automatically.

Use this guide before starting the v3 daemon with production traffic.

Before the upgrade

  1. Stop deployments and automatic image pruning.
  2. Back up Gordon's configuration, state, registry, databases, volumes, and secret store using your existing procedures.
  3. Record every current domain, image tag or digest, attachment, network relationship, volume, and secret key.
  4. Keep the previous signed Gordon binary and its matching configuration and state backup available for rollback.

Do not delete old containers, volumes, pass entries, or registry tags during the migration. Gordon's ownership-aware reconciliation and prune paths preserve resources whose ownership cannot be proven. Runtime-native cleanup commands and manual deletion do not provide that guarantee.

1. Update the installation configuration

Remove workload declarations that v3 no longer accepts from gordon.toml:

  • [routes];
  • [attachments];
  • [network_groups];
  • app-workload entries formerly declared as [[services]], and [service_routes] (installation-level [[services]] used for standalone L4 workloads stays valid);
  • [auto_route] and [auto_route_allowed_domains];
  • [previews].

Keep installation-level settings such as entrypoints, TLS, limits, registry policy, external routes, backup destinations, logging, and authentication.

Review every app image registry before applying manifests. v3 allows Docker Hub (docker.io, including its canonical pull host registry-1.docker.io), ghcr.io, quay.io, and Gordon's configured registry by default. Add each other registry, including private registries, as an exact hostname and optional non-default port:

[images]
allowed_registries = ["registry.internal:5000"]

Hostname matching is case-insensitive, ignores one trailing dot, and treats port 443 as the default. The allowlist is checked during manifest apply, deployment preflight, and immediately before pull. It controls registry names only: it does not prove that DNS resolves to a public IP or enforce runtime egress. Use firewall or runtime network policy when destination-level restrictions are required.

Create one app manifest per workload. See App Manifest.

name = "example-app"

[services.web]
image = "registry.example.com/example-app:1.2.3"

[services.web.secrets]
DATABASE_URL = "database-url"

[[services.web.http]]
host = "app.example.com"
port = 8080

The manifest contains secret names, never secret values.

Manifests written for an early v3 alpha used [[service]] with a name field; that shape is rejected, not converted. Rewrite each service as a keyed [services.<name>] table with [services.<name>.*] children — see Migrating a v3 alpha app manifest.

2. Migrate domain secrets

Gordon does not convert domain-scoped secrets into app secrets, and v3 has no command to list them. With the pass backend, inventory the old key names with pass ls gordon/env, declare them under [services.<name>.secrets], then apply the manifest before setting values:

gordon apps apply --file ./example-app.toml --remote production
printf 'DATABASE_URL=%s\n' "$(pass show gordon/env/app_example_com/DATABASE_URL)" \
  | gordon apps secrets set example-app --service web --stdin --remote production

Pass values through standard input. Do not put them in manifests, command arguments, logs, shell history, or temporary files. Repeat explicitly when several services need the same value.

Deploy and restart the app to verify that it uses the new app-scoped entries. Keep the old gordon/env/... entries throughout the rollback window; Gordon never uses them as a fallback. Refer to the pass documentation for password-store and GPG backup or restore procedures.

3. Transfer existing volume data

Gordon's reconciliation and prune paths leave existing unowned volumes untouched, but Gordon does not adopt them. After applying the manifest, let Gordon create the new app-owned volumes, then transfer the data with your container runtime's tools while the workload is stopped. Do not run runtime-wide volume prune, edit state.db, rename volumes, or alter ownership labels.

Use a database-native backup and restore for databases instead of copying live database files. Keep the old volumes unchanged until the new app has passed data, deploy, restart, and rollback-readiness checks. Refer to the Docker or Podman documentation for runtime-specific copy and inspection commands.

4. Apply and deploy apps

For every manifest:

gordon apps apply --file ./example-app.toml --remote production
gordon apps diff example-app --remote production
gordon apps deploy example-app --remote production
gordon apps status example-app --json --remote production

Push only uploads OCI content in v3; it does not deploy. Apply and deploy explicitly.

5. Replace tokens and automation

Replace removed route scopes with app scopes:

  • admin:apps:read for list, show, diff, and status;
  • admin:apps:write for apply, deploy, lifecycle, and app secrets.

Generate replacement CI and operator tokens with the minimum required scopes. Update automation that expected push-to-deploy, route mutation commands, previews, attachments, bootstrap, autoroute, pin, or rollback commands.

To roll back an application release in v3, apply a manifest containing the previous image tag and deploy it.

6. Validate before opening traffic

Verify all of the following:

  • each app is converged on the expected pinned digest;
  • HTTP, TCP, and UDP entrypoints work as applicable;
  • app secrets survive a restart and no value appears in output or logs;
  • named volumes contain the expected data after deploy and restart;
  • stopped intent survives a daemon restart;
  • retained and unknown resources remain untouched;
  • gordon images prune --dry-run reports protected app content correctly before any real prune;
  • the previous binary and matching data backup remain available.

Run destructive prune commands only after their dry-run candidate list matches the expected ownership and retention policy.

Security guarantees to rely on after the upgrade

  • Containment first. Keep auth.enabled = true and automatic registry prune disabled (images.prune.enabled = false) until the upgraded build is running and validated. A disabled-auth registry is local-only: public ingress cannot reach it through the proxy.
  • Network isolation. Every app container joins an app-owned private network; shared memberships come only from explicit manifest declarations, and container memory/CPU/PID limits are applied on create and recovery.
  • Registry content is repository-scoped. Pulls require the repository to have completed the upload of a blob, and manifests may only reference content that repository owns. Content uploaded by an older build has no durable ownership marker, so a repository whose blobs predate this build must re-push its images before they can be pulled again.
  • Runtime image prune needs released ownership. An app's pinned images are recorded as attached while deployed and released when the app is removed. Dangling images with no durable ownership record are never deleted, and image labels alone never authorize deletion.
  • Durable roots keep their closure. A digest pinned by active/desired state, an apply intent, an operation, or ownership retains its manifest, config, and layers even if the tag moves away.
  • Plaintext TLS policy. Hosts declared tls = always never receive plaintext traffic: they redirect when an HTTPS endpoint exists and are refused with 421 when none does.
  • Failure output is log-free. Mutation responses and operation lookup for admin:apps:read carry stable errors only; application diagnostics require admin:logs:read and are redacted before storage.

Rollback

A binary downgrade against v3 app state is unsupported. To roll back:

  1. stop the v3 daemon;
  2. restore the previous signed binary;
  3. restore its matching configuration and state backup as one set;
  4. restore secret-store entries if any were removed;
  5. restart with automatic pruning disabled;
  6. verify workloads and data before reopening traffic.

Do not attempt rollback by adopting unknown containers or volumes into v3 state.

source
1# Migrate to Gordon v3
2
3Gordon v3 replaces domain-based route workloads with declarative apps. The upgrade is intentionally explicit: Gordon does not infer app ownership from domains, does not adopt existing volumes, and does not copy domain secrets into app secrets automatically.
4
5Use this guide before starting the v3 daemon with production traffic.
6
7## Before the upgrade
8
91. Stop deployments and automatic image pruning.
102. Back up Gordon's configuration, state, registry, databases, volumes, and secret store using your existing procedures.
113. Record every current domain, image tag or digest, attachment, network relationship, volume, and secret key.
124. Keep the previous signed Gordon binary and its matching configuration and state backup available for rollback.
13
14Do not delete old containers, volumes, pass entries, or registry tags during the migration. Gordon's ownership-aware reconciliation and prune paths preserve resources whose ownership cannot be proven. Runtime-native cleanup commands and manual deletion do not provide that guarantee.
15
16## 1. Update the installation configuration
17
18Remove workload declarations that v3 no longer accepts from `gordon.toml`:
19
20- `[routes]`;
21- `[attachments]`;
22- `[network_groups]`;
23- app-workload entries formerly declared as `[[services]]`, and `[service_routes]` (installation-level `[[services]]` used for standalone L4 workloads stays valid);
24- `[auto_route]` and `[auto_route_allowed_domains]`;
25- `[previews]`.
26
27Keep installation-level settings such as entrypoints, TLS, limits, registry policy, external routes, backup destinations, logging, and authentication.
28
29Review every app image registry before applying manifests. v3 allows Docker Hub (`docker.io`, including its canonical pull host `registry-1.docker.io`), `ghcr.io`, `quay.io`, and Gordon's configured registry by default. Add each other registry, including private registries, as an exact hostname and optional non-default port:
30
31```toml
32[images]
33allowed_registries = ["registry.internal:5000"]
34```
35
36Hostname matching is case-insensitive, ignores one trailing dot, and treats port `443` as the default. The allowlist is checked during manifest apply, deployment preflight, and immediately before pull. It controls registry names only: it does not prove that DNS resolves to a public IP or enforce runtime egress. Use firewall or runtime network policy when destination-level restrictions are required.
37
38Create one app manifest per workload. See [App Manifest](./config/apps.md).
39
40```toml
41name = "example-app"
42
43[services.web]
44image = "registry.example.com/example-app:1.2.3"
45
46[services.web.secrets]
47DATABASE_URL = "database-url"
48
49[[services.web.http]]
50host = "app.example.com"
51port = 8080
52```
53
54The manifest contains secret **names**, never secret values.
55
56Manifests written for an early v3 alpha used `[[service]]` with a `name` field; that shape is rejected, not converted. Rewrite each service as a keyed `[services.<name>]` table with `[services.<name>.*]` children — see [Migrating a v3 alpha app manifest](./upgrading.md#migrating-a-v3-alpha-app-manifest).
57
58## 2. Migrate domain secrets
59
60Gordon does not convert domain-scoped secrets into app secrets, and v3 has no command to list them. With the `pass` backend, inventory the old key names with `pass ls gordon/env`, declare them under `[services.<name>.secrets]`, then apply the manifest before setting values:
61
62```bash
63gordon apps apply --file ./example-app.toml --remote production
64printf 'DATABASE_URL=%s\n' "$(pass show gordon/env/app_example_com/DATABASE_URL)" \
65  | gordon apps secrets set example-app --service web --stdin --remote production
66```
67
68Pass values through standard input. Do not put them in manifests, command arguments, logs, shell history, or temporary files. Repeat explicitly when several services need the same value.
69
70Deploy and restart the app to verify that it uses the new app-scoped entries. Keep the old `gordon/env/...` entries throughout the rollback window; Gordon never uses them as a fallback. Refer to the `pass` documentation for password-store and GPG backup or restore procedures.
71
72## 3. Transfer existing volume data
73
74Gordon's reconciliation and prune paths leave existing unowned volumes untouched, but Gordon does not adopt them. After applying the manifest, let Gordon create the new app-owned volumes, then transfer the data with your container runtime's tools while the workload is stopped. Do not run runtime-wide volume prune, edit `state.db`, rename volumes, or alter ownership labels.
75
76Use a database-native backup and restore for databases instead of copying live database files. Keep the old volumes unchanged until the new app has passed data, deploy, restart, and rollback-readiness checks. Refer to the Docker or Podman documentation for runtime-specific copy and inspection commands.
77
78## 4. Apply and deploy apps
79
80For every manifest:
81
82```bash
83gordon apps apply --file ./example-app.toml --remote production
84gordon apps diff example-app --remote production
85gordon apps deploy example-app --remote production
86gordon apps status example-app --json --remote production
87```
88
89Push only uploads OCI content in v3; it does not deploy. Apply and deploy explicitly.
90
91## 5. Replace tokens and automation
92
93Replace removed route scopes with app scopes:
94
95- `admin:apps:read` for list, show, diff, and status;
96- `admin:apps:write` for apply, deploy, lifecycle, and app secrets.
97
98Generate replacement CI and operator tokens with the minimum required scopes. Update automation that expected push-to-deploy, route mutation commands, previews, attachments, bootstrap, autoroute, pin, or rollback commands.
99
100To roll back an application release in v3, apply a manifest containing the previous image tag and deploy it.
101
102## 6. Validate before opening traffic
103
104Verify all of the following:
105
106- each app is converged on the expected pinned digest;
107- HTTP, TCP, and UDP entrypoints work as applicable;
108- app secrets survive a restart and no value appears in output or logs;
109- named volumes contain the expected data after deploy and restart;
110- stopped intent survives a daemon restart;
111- retained and unknown resources remain untouched;
112- `gordon images prune --dry-run` reports protected app content correctly before any real prune;
113- the previous binary and matching data backup remain available.
114
115Run destructive prune commands only after their dry-run candidate list matches the expected ownership and retention policy.
116
117## Security guarantees to rely on after the upgrade
118
119- **Containment first.** Keep `auth.enabled = true` and automatic registry prune disabled (`images.prune.enabled = false`) until the upgraded build is running and validated. A disabled-auth registry is local-only: public ingress cannot reach it through the proxy.
120- **Network isolation.** Every app container joins an app-owned private network; shared memberships come only from explicit manifest declarations, and container memory/CPU/PID limits are applied on create and recovery.
121- **Registry content is repository-scoped.** Pulls require the repository to have completed the upload of a blob, and manifests may only reference content that repository owns. Content uploaded by an older build has no durable ownership marker, so a repository whose blobs predate this build must re-push its images before they can be pulled again.
122- **Runtime image prune needs released ownership.** An app's pinned images are recorded as attached while deployed and released when the app is removed. Dangling images with no durable ownership record are never deleted, and image labels alone never authorize deletion.
123- **Durable roots keep their closure.** A digest pinned by active/desired state, an apply intent, an operation, or ownership retains its manifest, config, and layers even if the tag moves away.
124- **Plaintext TLS policy.** Hosts declared `tls = always` never receive plaintext traffic: they redirect when an HTTPS endpoint exists and are refused with `421` when none does.
125- **Failure output is log-free.** Mutation responses and operation lookup for `admin:apps:read` carry stable errors only; application diagnostics require `admin:logs:read` and are redacted before storage.
126
127## Rollback
128
129A binary downgrade against v3 app state is unsupported. To roll back:
130
1311. stop the v3 daemon;
1322. restore the previous signed binary;
1333. restore its matching configuration and state backup as one set;
1344. restore secret-store entries if any were removed;
1355. restart with automatic pruning disabled;
1366. verify workloads and data before reopening traffic.
137
138Do not attempt rollback by adopting unknown containers or volumes into v3 state.
139
140## Related
141
142- [Upgrading Gordon](./upgrading.md)
143- [App Manifest](./config/apps.md)
144- [Apps CLI](./cli/apps.md)
145- [Secrets](./config/secrets.md)
146- [Authentication](./config/auth.md)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close