v2.31.0-dev19 35467d7

upgrading.md RawHistoryFolder

Upgrading Gordon

This guide covers breaking changes and migration steps between major versions.

Route-Domain Validation

Route keys must be plain hostnames. Use inline tables like "app.example.com" = { image = "myapp:latest" }. Gordon still reads legacy http://... route entries for backward compatibility and rewrites them on the next save. Update [routes], CLI commands, and automation that reference the old values.

v2.30.0 to v2.31.0

Breaking: Default Port Changes

Gordon now defaults to unprivileged ports that work without root or firewall rules:

Setting Old default New default
server.port 80 8088
server.registry_port 5000 5000 (unchanged)
server.tls_port (n/a) 8443

If you relied on the old defaults (no explicit port in your config), add the port explicitly:

[server]
port = 80

Or set up firewall port forwarding to the new defaults:

sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
sudo firewall-cmd --reload

New: Internal Certificate Authority

Gordon now includes an internal CA for automatic on-demand TLS. Set tls_port = 0 to disable. See Server Configuration for details.

Required for Cloudflare/Proxy Setups: proxy_allowed_ips

The internal CA's HTTP onboarding gate rejects non-localhost HTTP requests by default. If Gordon sits behind Cloudflare or another reverse proxy, add the proxy's edge IPs to proxy_allowed_ips:

[server]
proxy_allowed_ips = [
  "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22",
  "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18",
  "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22",
  "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13",
  "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22",
]

Without this, all proxied HTTP traffic returns 403 Forbidden. Set tls_port = 0 to disable the internal CA and skip this requirement.

Breaking: server.gordon_domain Replaces server.registry_domain

Gordon now uses server.gordon_domain as the public registry and admin host. Migrate older configs that still set only server.registry_domain before restarting:

Before:

[server]
registry_domain = "gordon.example.com"

After:

[server]
gordon_domain = "gordon.example.com"

If you do not migrate, gordon status --remote ... and gordon routes list --remote ... can fail with /auth/token 404, and reg-domain/v2/ or /admin/status can return 404.

Staged Registry Host Rename

If you cannot rename the Gordon registry host in one step, keep the new host in server.gordon_domain and list the old Gordon registry hosts in server.legacy_registry_domains during the cutover:

[server]
gordon_domain = "gordon.example.com"
legacy_registry_domains = [
  "registry.example.com",
  "registry.example.com:5000",
]

Recommended rollout:

  1. Set gordon_domain to the new host.
  2. Add every old Gordon registry host that clients still use to legacy_registry_domains.
  3. Restart Gordon.
  4. Move Docker/Podman logins, pushes, pulls, and image references to the new host.
  5. Remove legacy_registry_domains after every client has moved.

During the transition, Gordon treats both the current and legacy hosts as its own registry for image matching and internal pulls, then saves canonical refs back to gordon_domain. Remote CLI and admin API traffic should use the new gordon_domain.

v2.16.0 to v2.30.0

Breaking: Password Authentication Removed

Gordon v2.30.0 removes password-based authentication entirely. Only token-based authentication is supported.

What changed:

  • auth.type = "password" is no longer accepted
  • auth.password and auth.password_hash config fields are removed
  • The gordon auth password hash CLI command is removed
  • The gordon auth login command now requires --token (no more interactive password prompt)
  • The /auth/password endpoint now returns 410 Gone
  • Long-lived tokens are no longer accepted on admin/registry endpoints — they must be exchanged for ephemeral tokens via /auth/token

New features:

  • auth.access_token_ttl configures the lifetime of ephemeral access tokens issued by /auth/token (default: "15m")
  • gordon auth show-token prints the stored token for a remote
  • gordon auth logout removes the stored token (with optional --revoke)
  • Automatic token exchange: the CLI transparently exchanges long-lived tokens for ephemeral ones before API calls
  • Admin scopes (admin:*:*, admin:routes:read, etc.) allow fine-grained access control for remote CLI operations

Migration steps:

  1. Before upgrading, generate a token on your current Gordon instance:

    gordon auth token generate --subject deploy --scopes "push,pull" --expiry 0
    

    Save this token securely. You will need it after upgrading. Admin scopes (admin:*:*) are only available after upgrading to v2.30.0 — regenerate your token with admin scopes after the upgrade if needed.

  2. Update remotes to use the generated token:

    gordon auth login --token <token>
    # or
    gordon remotes set-token prod <token>
    
  3. Update your config to remove password fields:

    Before (v2.16.0):

    [auth]
    enabled = true
    secrets_backend = "pass"
    username = "deploy"
    password_hash = "gordon/auth/password_hash"
    token_secret = "gordon/auth/token_secret"
    

    After (v2.30.0):

    [auth]
    enabled = true
    secrets_backend = "pass"
    token_secret = "gordon/auth/token_secret"
    access_token_ttl = "15m"
    
  4. Update CI/CD pipelines to use GORDON_TOKEN environment variable for authentication. See the deployment guides.

  5. Upgrade the binary and restart:

    curl -fsSL https://gordon.bnema.dev/install | bash
    systemctl --user restart gordon
    
  6. Verify the server starts without errors:

    journalctl --user -u gordon -f
    

New: Configurable Access Token TTL

Ephemeral access tokens issued by /auth/token now have a configurable lifetime via auth.access_token_ttl (default "15m", maximum "1h").

Tokens with a lifetime at or below MaxAccessTokenLifetime (1 hour) are treated as ephemeral: they skip token store validation for performance, which means they cannot be individually revoked. They become invalid only when they naturally expire. Shortening auth.access_token_ttl only reduces the exposure window; it does not enable per-token revocation. If you need explicit revocation, use a stored long-lived token instead of /auth/token.

[auth]
access_token_ttl = "30m"

New: Admin Scopes

Tokens can now include fine-grained admin scopes for remote CLI operations:

# Full admin access
gordon auth token generate --subject admin --scopes "push,pull,admin:*:*" --expiry 0

# Read-only monitoring
gordon auth token generate --subject monitor --scopes "admin:status:read" --expiry 30d

# CI deploy with route read + config write
gordon auth token generate --subject ci --scopes "push,pull,admin:routes:read,admin:config:write" --expiry 0

See Token Scopes for the full list.

v2.6.0 to v2.7.0

Breaking: Token Secret Required

Gordon v2.7.0 requires a token_secret for JWT authentication. The server will not start without it configured.

Error you'll see:

Error: token_secret is required for JWT token generation; set GORDON_AUTH_TOKEN_SECRET environment variable or configure auth.token_secret

Choose one of these migration options:

Option A: Environment Variable (simplest)

# Generate a random secret
export GORDON_AUTH_TOKEN_SECRET="$(openssl rand -base64 32)"

# Add to your shell profile or systemd service
echo 'export GORDON_AUTH_TOKEN_SECRET="your-secret"' >> ~/.bashrc

For systemd services:

mkdir -p ~/.config/systemd/user/gordon.service.d
cat > ~/.config/systemd/user/gordon.service.d/token.conf << EOF
[Service]
Environment="GORDON_AUTH_TOKEN_SECRET=$(openssl rand -base64 32)"
EOF
systemctl --user daemon-reload
systemctl --user restart gordon
# Generate and store secret in pass
openssl rand -base64 32 | pass insert -e gordon/auth/token_secret

# Update your gordon.toml
[auth]
enabled = true
secrets_backend = "pass"
token_secret = "gordon/auth/token_secret"

Option C: Config File with Unsafe Backend (development only)

# Generate secret file
mkdir -p ~/.gordon/secrets
openssl rand -base64 32 > ~/.gordon/secrets/token_secret
chmod 600 ~/.gordon/secrets/token_secret
[auth]
enabled = true
secrets_backend = "unsafe"
token_secret = "token_secret"

Breaking: Unsafe Token Store Warning

Using secrets_backend = "unsafe" now logs a warning on startup:

WRN using unsafe secrets backend - secrets are stored in plain text

This is intentional to encourage secure secret storage in production. The warning can be ignored for development.

New Features

  • Attachment secrets discovery: gordon secrets list <domain> now shows secrets for attachment containers
  • Auth login command: gordon auth login --remote <name> for token authentication
  • Rate limiting: Configurable rate limits under [api.rate_limit]

Security Improvements

v2.7.0 includes a comprehensive security audit with:

  • JWT tokens now include "not before" (nbf) claim
  • SSRF protection for external routes
  • Security headers middleware
  • Rate limiting on registry and token endpoints
  • Command injection prevention in pass provider
  • Path traversal prevention in secrets store

v2.5.0 to v2.6.0

Breaking: Config Restructure

The [secrets] and [registry_auth] sections were merged into [auth].

Before (v2.5.0):

[secrets]
backend = "pass"

[registry_auth]
enabled = true
type = "password"
password_hash = "gordon/registry/password_hash"

After (v2.6.0+):

[auth]
enabled = true
secrets_backend = "pass"
password_hash = "gordon/auth/password_hash"

Breaking: Auth Enabled by Default

Gordon keeps auth enabled by default. If you set auth.enabled = false, Gordon runs in local-only mode (/admin/* disabled, /v2/* loopback-only).

Breaking: Secret Paths Changed

If using pass or sops, update your secret paths:

  • gordon/registry/* → gordon/auth/*

General Upgrade Process

  1. Read the changelog for your target version
  2. Backup your config before upgrading
  3. Test in staging if possible
  4. Upgrade the binary:
    curl -fsSL https://gordon.bnema.dev/install | bash
    
  5. Restart Gordon:
    systemctl --user restart gordon
    
  6. Check logs for any errors:
    gordon logs
    

Getting Help

source
1# Upgrading Gordon
2
3This guide covers breaking changes and migration steps between major versions.
4
5## Route-Domain Validation
6
7Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values.
8
9## v2.30.0 to v2.31.0
10
11### Breaking: Default Port Changes
12
13Gordon now defaults to unprivileged ports that work without root or firewall rules:
14
15| Setting | Old default | New default |
16|---------|-------------|-------------|
17| `server.port` | `80` | `8088` |
18| `server.registry_port` | `5000` | `5000` (unchanged) |
19| `server.tls_port` | *(n/a)* | `8443` |
20
21**If you relied on the old defaults** (no explicit `port` in your config), add the port explicitly:
22
23```toml
24[server]
25port = 80
26```
27
28Or set up firewall port forwarding to the new defaults:
29
30```bash
31sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088
32sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
33sudo firewall-cmd --reload
34```
35
36### New: Internal Certificate Authority
37
38Gordon now includes an internal CA for automatic on-demand TLS. Set `tls_port = 0` to disable. See [Server Configuration](./config/server.md#internal-ca-and-tls) for details.
39
40### Required for Cloudflare/Proxy Setups: `proxy_allowed_ips`
41
42The internal CA's HTTP onboarding gate rejects non-localhost HTTP requests by default. If Gordon sits behind Cloudflare or another reverse proxy, add the proxy's edge IPs to `proxy_allowed_ips`:
43
44```toml
45[server]
46proxy_allowed_ips = [
47  "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22",
48  "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18",
49  "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22",
50  "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13",
51  "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22",
52]
53```
54
55Without this, all proxied HTTP traffic returns `403 Forbidden`. Set `tls_port = 0` to disable the internal CA and skip this requirement.
56
57### Breaking: `server.gordon_domain` Replaces `server.registry_domain`
58
59Gordon now uses `server.gordon_domain` as the public registry and admin host. Migrate older configs that still set only `server.registry_domain` before restarting:
60
61**Before:**
62
63```toml
64[server]
65registry_domain = "gordon.example.com"
66```
67
68**After:**
69
70```toml
71[server]
72gordon_domain = "gordon.example.com"
73```
74
75If you do not migrate, `gordon status --remote ...` and `gordon routes list --remote ...` can fail with `/auth/token` `404`, and `reg-domain/v2/` or `/admin/status` can return `404`.
76
77### Staged Registry Host Rename
78
79If you cannot rename the Gordon registry host in one step, keep the new host in `server.gordon_domain` and list the old Gordon registry hosts in `server.legacy_registry_domains` during the cutover:
80
81```toml
82[server]
83gordon_domain = "gordon.example.com"
84legacy_registry_domains = [
85  "registry.example.com",
86  "registry.example.com:5000",
87]
88```
89
90Recommended rollout:
91
921. Set `gordon_domain` to the new host.
932. Add every old Gordon registry host that clients still use to `legacy_registry_domains`.
943. Restart Gordon.
954. Move Docker/Podman logins, pushes, pulls, and image references to the new host.
965. Remove `legacy_registry_domains` after every client has moved.
97
98During the transition, Gordon treats both the current and legacy hosts as its own registry for image matching and internal pulls, then saves canonical refs back to `gordon_domain`. Remote CLI and admin API traffic should use the new `gordon_domain`.
99
100## v2.16.0 to v2.30.0
101
102### Breaking: Password Authentication Removed
103
104Gordon v2.30.0 removes password-based authentication entirely. Only token-based authentication is supported.
105
106**What changed:**
107
108- `auth.type = "password"` is no longer accepted
109- `auth.password` and `auth.password_hash` config fields are removed
110- The `gordon auth password hash` CLI command is removed
111- The `gordon auth login` command now requires `--token` (no more interactive password prompt)
112- The `/auth/password` endpoint now returns `410 Gone`
113- Long-lived tokens are no longer accepted on admin/registry endpoints — they must be exchanged for ephemeral tokens via `/auth/token`
114
115**New features:**
116
117- `auth.access_token_ttl` configures the lifetime of ephemeral access tokens issued by `/auth/token` (default: `"15m"`)
118- `gordon auth show-token` prints the stored token for a remote
119- `gordon auth logout` removes the stored token (with optional `--revoke`)
120- Automatic token exchange: the CLI transparently exchanges long-lived tokens for ephemeral ones before API calls
121- Admin scopes (`admin:*:*`, `admin:routes:read`, etc.) allow fine-grained access control for remote CLI operations
122
123**Migration steps:**
124
1251. **Before upgrading**, generate a token on your current Gordon instance:
126
127   ```bash
128   gordon auth token generate --subject deploy --scopes "push,pull" --expiry 0
129   ```
130
131   Save this token securely. You will need it after upgrading. Admin scopes (`admin:*:*`) are only available after upgrading to v2.30.0 — regenerate your token with admin scopes after the upgrade if needed.
132
1332. **Update remotes** to use the generated token:
134
135   ```bash
136   gordon auth login --token <token>
137   # or
138   gordon remotes set-token prod <token>
139   ```
140
1413. **Update your config** to remove password fields:
142
143   **Before (v2.16.0):**
144
145   ```toml
146   [auth]
147   enabled = true
148   secrets_backend = "pass"
149   username = "deploy"
150   password_hash = "gordon/auth/password_hash"
151   token_secret = "gordon/auth/token_secret"
152   ```
153
154   **After (v2.30.0):**
155
156   ```toml
157   [auth]
158   enabled = true
159   secrets_backend = "pass"
160   token_secret = "gordon/auth/token_secret"
161   access_token_ttl = "15m"
162   ```
163
1644. **Update CI/CD pipelines** to use `GORDON_TOKEN` environment variable for authentication. See the [deployment guides](./deployment/index.md).
165
1665. **Upgrade the binary** and restart:
167
168   ```bash
169   curl -fsSL https://gordon.bnema.dev/install | bash
170   systemctl --user restart gordon
171   ```
172
1736. **Verify** the server starts without errors:
174
175   ```bash
176   journalctl --user -u gordon -f
177   ```
178
179### New: Configurable Access Token TTL
180
181Ephemeral access tokens issued by `/auth/token` now have a configurable lifetime via `auth.access_token_ttl` (default `"15m"`, maximum `"1h"`).
182
183Tokens with a lifetime at or below `MaxAccessTokenLifetime` (1 hour) are treated as ephemeral: they skip token store validation for performance, which means they **cannot be individually revoked**. They become invalid only when they naturally expire. Shortening `auth.access_token_ttl` only reduces the exposure window; it does not enable per-token revocation. If you need explicit revocation, use a stored long-lived token instead of `/auth/token`.
184
185```toml
186[auth]
187access_token_ttl = "30m"
188```
189
190### New: Admin Scopes
191
192Tokens can now include fine-grained admin scopes for remote CLI operations:
193
194```bash
195# Full admin access
196gordon auth token generate --subject admin --scopes "push,pull,admin:*:*" --expiry 0
197
198# Read-only monitoring
199gordon auth token generate --subject monitor --scopes "admin:status:read" --expiry 30d
200
201# CI deploy with route read + config write
202gordon auth token generate --subject ci --scopes "push,pull,admin:routes:read,admin:config:write" --expiry 0
203```
204
205See [Token Scopes](./config/auth.md#token-scopes) for the full list.
206
207## v2.6.0 to v2.7.0
208
209### Breaking: Token Secret Required
210
211Gordon v2.7.0 requires a `token_secret` for JWT authentication. The server will not start without it configured.
212
213**Error you'll see:**
214```
215Error: token_secret is required for JWT token generation; set GORDON_AUTH_TOKEN_SECRET environment variable or configure auth.token_secret
216```
217
218**Choose one of these migration options:**
219
220#### Option A: Environment Variable (simplest)
221
222```bash
223# Generate a random secret
224export GORDON_AUTH_TOKEN_SECRET="$(openssl rand -base64 32)"
225
226# Add to your shell profile or systemd service
227echo 'export GORDON_AUTH_TOKEN_SECRET="your-secret"' >> ~/.bashrc
228```
229
230For systemd services:
231```bash
232mkdir -p ~/.config/systemd/user/gordon.service.d
233cat > ~/.config/systemd/user/gordon.service.d/token.conf << EOF
234[Service]
235Environment="GORDON_AUTH_TOKEN_SECRET=$(openssl rand -base64 32)"
236EOF
237systemctl --user daemon-reload
238systemctl --user restart gordon
239```
240
241#### Option B: Config File with Pass (recommended for production)
242
243```bash
244# Generate and store secret in pass
245openssl rand -base64 32 | pass insert -e gordon/auth/token_secret
246
247# Update your gordon.toml
248```
249
250```toml
251[auth]
252enabled = true
253secrets_backend = "pass"
254token_secret = "gordon/auth/token_secret"
255```
256
257#### Option C: Config File with Unsafe Backend (development only)
258
259```bash
260# Generate secret file
261mkdir -p ~/.gordon/secrets
262openssl rand -base64 32 > ~/.gordon/secrets/token_secret
263chmod 600 ~/.gordon/secrets/token_secret
264```
265
266```toml
267[auth]
268enabled = true
269secrets_backend = "unsafe"
270token_secret = "token_secret"
271```
272
273### Breaking: Unsafe Token Store Warning
274
275Using `secrets_backend = "unsafe"` now logs a warning on startup:
276```
277WRN using unsafe secrets backend - secrets are stored in plain text
278```
279
280This is intentional to encourage secure secret storage in production. The warning can be ignored for development.
281
282### New Features
283
284- **Attachment secrets discovery**: `gordon secrets list <domain>` now shows secrets for attachment containers
285- **Auth login command**: `gordon auth login --remote <name>` for token authentication
286- **Rate limiting**: Configurable rate limits under `[api.rate_limit]`
287
288### Security Improvements
289
290v2.7.0 includes a comprehensive security audit with:
291
292- JWT tokens now include "not before" (nbf) claim
293- SSRF protection for external routes
294- Security headers middleware
295- Rate limiting on registry and token endpoints
296- Command injection prevention in pass provider
297- Path traversal prevention in secrets store
298
299## v2.5.0 to v2.6.0
300
301### Breaking: Config Restructure
302
303The `[secrets]` and `[registry_auth]` sections were merged into `[auth]`.
304
305**Before (v2.5.0):**
306```toml
307[secrets]
308backend = "pass"
309
310[registry_auth]
311enabled = true
312type = "password"
313password_hash = "gordon/registry/password_hash"
314```
315
316**After (v2.6.0+):**
317```toml
318[auth]
319enabled = true
320secrets_backend = "pass"
321password_hash = "gordon/auth/password_hash"
322```
323
324### Breaking: Auth Enabled by Default
325
326Gordon keeps auth enabled by default. If you set `auth.enabled = false`, Gordon runs in local-only mode (`/admin/*` disabled, `/v2/*` loopback-only).
327
328### Breaking: Secret Paths Changed
329
330If using pass or sops, update your secret paths:
331- `gordon/registry/*` → `gordon/auth/*`
332
333## General Upgrade Process
334
3351. **Read the changelog** for your target version
3362. **Backup your config** before upgrading
3373. **Test in staging** if possible
3384. **Upgrade the binary**:
339   ```bash
340   curl -fsSL https://gordon.bnema.dev/install | bash
341   ```
3425. **Restart Gordon**:
343   ```bash
344   systemctl --user restart gordon
345   ```
3466. **Check logs** for any errors:
347   ```bash
348   gordon logs
349   ```
350
351## Getting Help
352
353- [GitHub Issues](https://github.com/bnema/gordon/issues)
354- [Documentation](https://gordon.bnema.dev/docs)

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