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.
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:
-
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## 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)