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:
- Set
gordon_domain to the new host.
- Add every old Gordon registry host that clients still use to
legacy_registry_domains.
- Restart Gordon.
- Move Docker/Podman logins, pushes, pulls, and image references to the new host.
- 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.
Breaking: gordon rollback Renamed to gordon pin
If you have scripts or runbooks using gordon rollback, switch them to gordon pin:
gordon pin app.example.com
gordon pin app.example.com --tag v2.30.1
gordon pin list app.example.com
Breaking-ish: routes list Is Inventory-Only
gordon routes list now shows domain/image inventory only. Detailed runtime state, HTTP probe status, and attachment status moved to gordon routes status.
If you have automation parsing status-like fields from routes list, migrate it to routes status --json.
New: Public ACME TLS
Gordon can now obtain public certificates with [tls.acme] using either http-01 or cloudflare-dns-01.
server.tls_port must stay enabled (> 0)
http-01 needs public HTTP reachability for each hostname being validated
cloudflare-dns-01 needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes
obtain_batch_size limits new ACME orders per reconcile pass to reduce rate-limit spikes
New: DNS Resolver Settings for ACME DNS-01
[dns] controls the recursive resolvers Gordon uses to verify public DNS propagation for ACME DNS-01.
Add this if you need non-default resolvers, split-horizon-aware testing, or a slower propagation window:
[dns]
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
propagation_timeout = "5m"
polling_interval = "5s"
Runtime Change: Managed Containers Use Restart Policy always
Newly created managed route containers now use the runtime restart policy always. Existing containers pick this up on redeploy/recreate.
This improves crash recovery and keeps Docker/Podman behavior aligned with Gordon's startup reconciliation.
New: Saved Remote Inference for Targeted Commands
When you do not pass --remote, Gordon can now infer a saved remote for some target-based commands when exactly one configured remote matches the route, image, attachment target, or repository.
If multiple remotes match or probing fails, Gordon now stops and asks you to pass --remote explicitly instead of guessing.
New: Dedicated Access Log
You can now enable logging.access_log.* for a dedicated HTTP access log separate from the main process log. This is useful for request auditing, GoAccess, CrowdSec, or fail2ban-style tooling.
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 locally
- 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:
-
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.
-
Update remotes to use the generated token:
gordon auth login --token <token>
# or
gordon remotes set-token prod <token>
-
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"
-
Update CI/CD pipelines to use GORDON_TOKEN environment variable for authentication. See the deployment guides.
-
Upgrade the binary and restart:
curl -fsSL https://gordon.bnema.dev/install | bash
systemctl --user restart gordon
-
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
Option B: Config File with Pass (recommended for production)
# 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
- Read the changelog for your target version
- Backup your config before upgrading
- Test in staging if possible
- Upgrade the binary:
curl -fsSL https://gordon.bnema.dev/install | bash
- Restart Gordon:
systemctl --user restart gordon
- 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### Breaking: `gordon rollback` Renamed to `gordon pin`
101
102If you have scripts or runbooks using `gordon rollback`, switch them to `gordon pin`:
103
104```bash
105gordon pin app.example.com
106gordon pin app.example.com --tag v2.30.1
107gordon pin list app.example.com
108```
109
110### Breaking-ish: `routes list` Is Inventory-Only
111
112`gordon routes list` now shows domain/image inventory only. Detailed runtime state, HTTP probe status, and attachment status moved to `gordon routes status`.
113
114If you have automation parsing status-like fields from `routes list`, migrate it to `routes status --json`.
115
116### New: Public ACME TLS
117
118Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`.
119
120- `server.tls_port` must stay enabled (`> 0`)
121- `http-01` needs public HTTP reachability for each hostname being validated
122- `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes
123- `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes
124
125### New: DNS Resolver Settings for ACME DNS-01
126
127`[dns]` controls the recursive resolvers Gordon uses to verify public DNS propagation for ACME DNS-01.
128
129Add this if you need non-default resolvers, split-horizon-aware testing, or a slower propagation window:
130
131```toml
132[dns]
133resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
134propagation_timeout = "5m"
135polling_interval = "5s"
136```
137
138### Runtime Change: Managed Containers Use Restart Policy `always`
139
140Newly created managed route containers now use the runtime restart policy `always`. Existing containers pick this up on redeploy/recreate.
141
142This improves crash recovery and keeps Docker/Podman behavior aligned with Gordon's startup reconciliation.
143
144### New: Saved Remote Inference for Targeted Commands
145
146When you do not pass `--remote`, Gordon can now infer a saved remote for some target-based commands when exactly one configured remote matches the route, image, attachment target, or repository.
147
148If multiple remotes match or probing fails, Gordon now stops and asks you to pass `--remote` explicitly instead of guessing.
149
150### New: Dedicated Access Log
151
152You can now enable `logging.access_log.*` for a dedicated HTTP access log separate from the main process log. This is useful for request auditing, GoAccess, CrowdSec, or fail2ban-style tooling.
153
154## v2.16.0 to v2.30.0
155
156### Breaking: Password Authentication Removed
157
158Gordon v2.30.0 removes password-based authentication entirely. Only token-based authentication is supported.
159
160**What changed:**
161
162- `auth.type = "password"` is no longer accepted
163- `auth.password` and `auth.password_hash` config fields are removed
164- The `gordon auth password hash` CLI command is removed
165- The `gordon auth login` command now requires `--token` (no more interactive password prompt)
166- The `/auth/password` endpoint now returns `410 Gone`
167- Long-lived tokens are no longer accepted on admin/registry endpoints — they must be exchanged for ephemeral tokens via `/auth/token`
168
169**New features:**
170
171- `auth.access_token_ttl` configures the lifetime of ephemeral access tokens issued by `/auth/token` (default: `"15m"`)
172- `gordon auth show-token` prints the stored token for a remote
173- `gordon auth logout` removes the stored token locally
174- Automatic token exchange: the CLI transparently exchanges long-lived tokens for ephemeral ones before API calls
175- Admin scopes (`admin:*:*`, `admin:routes:read`, etc.) allow fine-grained access control for remote CLI operations
176
177**Migration steps:**
178
1791. **Before upgrading**, generate a token on your current Gordon instance:
180
181 ```bash
182 gordon auth token generate --subject deploy --scopes "push,pull" --expiry 0
183 ```
184
185 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.
186
1872. **Update remotes** to use the generated token:
188
189 ```bash
190 gordon auth login --token <token>
191 # or
192 gordon remotes set-token prod <token>
193 ```
194
1953. **Update your config** to remove password fields:
196
197 **Before (v2.16.0):**
198
199 ```toml
200 [auth]
201 enabled = true
202 secrets_backend = "pass"
203 username = "deploy"
204 password_hash = "gordon/auth/password_hash"
205 token_secret = "gordon/auth/token_secret"
206 ```
207
208 **After (v2.30.0):**
209
210 ```toml
211 [auth]
212 enabled = true
213 secrets_backend = "pass"
214 token_secret = "gordon/auth/token_secret"
215 access_token_ttl = "15m"
216 ```
217
2184. **Update CI/CD pipelines** to use `GORDON_TOKEN` environment variable for authentication. See the [deployment guides](./deployment/index.md).
219
2205. **Upgrade the binary** and restart:
221
222 ```bash
223 curl -fsSL https://gordon.bnema.dev/install | bash
224 systemctl --user restart gordon
225 ```
226
2276. **Verify** the server starts without errors:
228
229 ```bash
230 journalctl --user -u gordon -f
231 ```
232
233### New: Configurable Access Token TTL
234
235Ephemeral access tokens issued by `/auth/token` now have a configurable lifetime via `auth.access_token_ttl` (default `"15m"`, maximum `"1h"`).
236
237Tokens 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`.
238
239```toml
240[auth]
241access_token_ttl = "30m"
242```
243
244### New: Admin Scopes
245
246Tokens can now include fine-grained admin scopes for remote CLI operations:
247
248```bash
249# Full admin access
250gordon auth token generate --subject admin --scopes "push,pull,admin:*:*" --expiry 0
251
252# Read-only monitoring
253gordon auth token generate --subject monitor --scopes "admin:status:read" --expiry 30d
254
255# CI deploy with route read + config write
256gordon auth token generate --subject ci --scopes "push,pull,admin:routes:read,admin:config:write" --expiry 0
257```
258
259See [Token Scopes](./config/auth.md#token-scopes) for the full list.
260
261## v2.6.0 to v2.7.0
262
263### Breaking: Token Secret Required
264
265Gordon v2.7.0 requires a `token_secret` for JWT authentication. The server will not start without it configured.
266
267**Error you'll see:**
268```
269Error: token_secret is required for JWT token generation; set GORDON_AUTH_TOKEN_SECRET environment variable or configure auth.token_secret
270```
271
272**Choose one of these migration options:**
273
274#### Option A: Environment Variable (simplest)
275
276```bash
277# Generate a random secret
278export GORDON_AUTH_TOKEN_SECRET="$(openssl rand -base64 32)"
279
280# Add to your shell profile or systemd service
281echo 'export GORDON_AUTH_TOKEN_SECRET="your-secret"' >> ~/.bashrc
282```
283
284For systemd services:
285```bash
286mkdir -p ~/.config/systemd/user/gordon.service.d
287cat > ~/.config/systemd/user/gordon.service.d/token.conf << EOF
288[Service]
289Environment="GORDON_AUTH_TOKEN_SECRET=$(openssl rand -base64 32)"
290EOF
291systemctl --user daemon-reload
292systemctl --user restart gordon
293```
294
295#### Option B: Config File with Pass (recommended for production)
296
297```bash
298# Generate and store secret in pass
299openssl rand -base64 32 | pass insert -e gordon/auth/token_secret
300
301# Update your gordon.toml
302```
303
304```toml
305[auth]
306enabled = true
307secrets_backend = "pass"
308token_secret = "gordon/auth/token_secret"
309```
310
311#### Option C: Config File with Unsafe Backend (development only)
312
313```bash
314# Generate secret file
315mkdir -p ~/.gordon/secrets
316openssl rand -base64 32 > ~/.gordon/secrets/token_secret
317chmod 600 ~/.gordon/secrets/token_secret
318```
319
320```toml
321[auth]
322enabled = true
323secrets_backend = "unsafe"
324token_secret = "token_secret"
325```
326
327### Breaking: Unsafe Token Store Warning
328
329Using `secrets_backend = "unsafe"` now logs a warning on startup:
330```
331WRN using unsafe secrets backend - secrets are stored in plain text
332```
333
334This is intentional to encourage secure secret storage in production. The warning can be ignored for development.
335
336### New Features
337
338- **Attachment secrets discovery**: `gordon secrets list <domain>` now shows secrets for attachment containers
339- **Auth login command**: `gordon auth login --remote <name>` for token authentication
340- **Rate limiting**: Configurable rate limits under `[api.rate_limit]`
341
342### Security Improvements
343
344v2.7.0 includes a comprehensive security audit with:
345
346- JWT tokens now include "not before" (nbf) claim
347- SSRF protection for external routes
348- Security headers middleware
349- Rate limiting on registry and token endpoints
350- Command injection prevention in pass provider
351- Path traversal prevention in secrets store
352
353## v2.5.0 to v2.6.0
354
355### Breaking: Config Restructure
356
357The `[secrets]` and `[registry_auth]` sections were merged into `[auth]`.
358
359**Before (v2.5.0):**
360```toml
361[secrets]
362backend = "pass"
363
364[registry_auth]
365enabled = true
366type = "password"
367password_hash = "gordon/registry/password_hash"
368```
369
370**After (v2.6.0+):**
371```toml
372[auth]
373enabled = true
374secrets_backend = "pass"
375password_hash = "gordon/auth/password_hash"
376```
377
378### Breaking: Auth Enabled by Default
379
380Gordon keeps auth enabled by default. If you set `auth.enabled = false`, Gordon runs in local-only mode (`/admin/*` disabled, `/v2/*` loopback-only).
381
382### Breaking: Secret Paths Changed
383
384If using pass or sops, update your secret paths:
385- `gordon/registry/*` → `gordon/auth/*`
386
387## General Upgrade Process
388
3891. **Read the changelog** for your target version
3902. **Backup your config** before upgrading
3913. **Test in staging** if possible
3924. **Upgrade the binary**:
393 ```bash
394 curl -fsSL https://gordon.bnema.dev/install | bash
395 ```
3965. **Restart Gordon**:
397 ```bash
398 systemctl --user restart gordon
399 ```
4006. **Check logs** for any errors:
401 ```bash
402 gordon logs
403 ```
404
405## Getting Help
406
407- [GitHub Issues](https://github.com/bnema/gordon/issues)
408- [Documentation](https://gordon.bnema.dev/docs)