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.
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## v2.16.0 to v2.30.0
78
79### Breaking: Password Authentication Removed
80
81Gordon v2.30.0 removes password-based authentication entirely. Only token-based authentication is supported.
82
83**What changed:**
84
85- `auth.type = "password"` is no longer accepted
86- `auth.password` and `auth.password_hash` config fields are removed
87- The `gordon auth password hash` CLI command is removed
88- The `gordon auth login` command now requires `--token` (no more interactive password prompt)
89- The `/auth/password` endpoint now returns `410 Gone`
90- Long-lived tokens are no longer accepted on admin/registry endpoints — they must be exchanged for ephemeral tokens via `/auth/token`
91
92**New features:**
93
94- `auth.access_token_ttl` configures the lifetime of ephemeral access tokens issued by `/auth/token` (default: `"15m"`)
95- `gordon auth show-token` prints the stored token for a remote
96- `gordon auth logout` removes the stored token (with optional `--revoke`)
97- Automatic token exchange: the CLI transparently exchanges long-lived tokens for ephemeral ones before API calls
98- Admin scopes (`admin:*:*`, `admin:routes:read`, etc.) allow fine-grained access control for remote CLI operations
99
100**Migration steps:**
101
1021. **Before upgrading**, generate a token on your current Gordon instance:
103
104 ```bash
105 gordon auth token generate --subject deploy --scopes "push,pull" --expiry 0
106 ```
107
108 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.
109
1102. **Update remotes** to use the generated token:
111
112 ```bash
113 gordon auth login --token <token>
114 # or
115 gordon remotes set-token prod <token>
116 ```
117
1183. **Update your config** to remove password fields:
119
120 **Before (v2.16.0):**
121
122 ```toml
123 [auth]
124 enabled = true
125 secrets_backend = "pass"
126 username = "deploy"
127 password_hash = "gordon/auth/password_hash"
128 token_secret = "gordon/auth/token_secret"
129 ```
130
131 **After (v2.30.0):**
132
133 ```toml
134 [auth]
135 enabled = true
136 secrets_backend = "pass"
137 token_secret = "gordon/auth/token_secret"
138 access_token_ttl = "15m"
139 ```
140
1414. **Update CI/CD pipelines** to use `GORDON_TOKEN` environment variable for authentication. See the [deployment guides](./deployment/index.md).
142
1435. **Upgrade the binary** and restart:
144
145 ```bash
146 curl -fsSL https://gordon.bnema.dev/install | bash
147 systemctl --user restart gordon
148 ```
149
1506. **Verify** the server starts without errors:
151
152 ```bash
153 journalctl --user -u gordon -f
154 ```
155
156### New: Configurable Access Token TTL
157
158Ephemeral access tokens issued by `/auth/token` now have a configurable lifetime via `auth.access_token_ttl` (default `"15m"`, maximum `"1h"`).
159
160Tokens 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`.
161
162```toml
163[auth]
164access_token_ttl = "30m"
165```
166
167### New: Admin Scopes
168
169Tokens can now include fine-grained admin scopes for remote CLI operations:
170
171```bash
172# Full admin access
173gordon auth token generate --subject admin --scopes "push,pull,admin:*:*" --expiry 0
174
175# Read-only monitoring
176gordon auth token generate --subject monitor --scopes "admin:status:read" --expiry 30d
177
178# CI deploy with route read + config write
179gordon auth token generate --subject ci --scopes "push,pull,admin:routes:read,admin:config:write" --expiry 0
180```
181
182See [Token Scopes](./config/auth.md#token-scopes) for the full list.
183
184## v2.6.0 to v2.7.0
185
186### Breaking: Token Secret Required
187
188Gordon v2.7.0 requires a `token_secret` for JWT authentication. The server will not start without it configured.
189
190**Error you'll see:**
191```
192Error: token_secret is required for JWT token generation; set GORDON_AUTH_TOKEN_SECRET environment variable or configure auth.token_secret
193```
194
195**Choose one of these migration options:**
196
197#### Option A: Environment Variable (simplest)
198
199```bash
200# Generate a random secret
201export GORDON_AUTH_TOKEN_SECRET="$(openssl rand -base64 32)"
202
203# Add to your shell profile or systemd service
204echo 'export GORDON_AUTH_TOKEN_SECRET="your-secret"' >> ~/.bashrc
205```
206
207For systemd services:
208```bash
209mkdir -p ~/.config/systemd/user/gordon.service.d
210cat > ~/.config/systemd/user/gordon.service.d/token.conf << EOF
211[Service]
212Environment="GORDON_AUTH_TOKEN_SECRET=$(openssl rand -base64 32)"
213EOF
214systemctl --user daemon-reload
215systemctl --user restart gordon
216```
217
218#### Option B: Config File with Pass (recommended for production)
219
220```bash
221# Generate and store secret in pass
222openssl rand -base64 32 | pass insert -e gordon/auth/token_secret
223
224# Update your gordon.toml
225```
226
227```toml
228[auth]
229enabled = true
230secrets_backend = "pass"
231token_secret = "gordon/auth/token_secret"
232```
233
234#### Option C: Config File with Unsafe Backend (development only)
235
236```bash
237# Generate secret file
238mkdir -p ~/.gordon/secrets
239openssl rand -base64 32 > ~/.gordon/secrets/token_secret
240chmod 600 ~/.gordon/secrets/token_secret
241```
242
243```toml
244[auth]
245enabled = true
246secrets_backend = "unsafe"
247token_secret = "token_secret"
248```
249
250### Breaking: Unsafe Token Store Warning
251
252Using `secrets_backend = "unsafe"` now logs a warning on startup:
253```
254WRN using unsafe secrets backend - secrets are stored in plain text
255```
256
257This is intentional to encourage secure secret storage in production. The warning can be ignored for development.
258
259### New Features
260
261- **Attachment secrets discovery**: `gordon secrets list <domain>` now shows secrets for attachment containers
262- **Auth login command**: `gordon auth login --remote <name>` for token authentication
263- **Rate limiting**: Configurable rate limits under `[api.rate_limit]`
264
265### Security Improvements
266
267v2.7.0 includes a comprehensive security audit with:
268
269- JWT tokens now include "not before" (nbf) claim
270- SSRF protection for external routes
271- Security headers middleware
272- Rate limiting on registry and token endpoints
273- Command injection prevention in pass provider
274- Path traversal prevention in secrets store
275
276## v2.5.0 to v2.6.0
277
278### Breaking: Config Restructure
279
280The `[secrets]` and `[registry_auth]` sections were merged into `[auth]`.
281
282**Before (v2.5.0):**
283```toml
284[secrets]
285backend = "pass"
286
287[registry_auth]
288enabled = true
289type = "password"
290password_hash = "gordon/registry/password_hash"
291```
292
293**After (v2.6.0+):**
294```toml
295[auth]
296enabled = true
297secrets_backend = "pass"
298password_hash = "gordon/auth/password_hash"
299```
300
301### Breaking: Auth Enabled by Default
302
303Gordon keeps auth enabled by default. If you set `auth.enabled = false`, Gordon runs in local-only mode (`/admin/*` disabled, `/v2/*` loopback-only).
304
305### Breaking: Secret Paths Changed
306
307If using pass or sops, update your secret paths:
308- `gordon/registry/*` → `gordon/auth/*`
309
310## General Upgrade Process
311
3121. **Read the changelog** for your target version
3132. **Backup your config** before upgrading
3143. **Test in staging** if possible
3154. **Upgrade the binary**:
316 ```bash
317 curl -fsSL https://gordon.bnema.dev/install | bash
318 ```
3195. **Restart Gordon**:
320 ```bash
321 systemctl --user restart gordon
322 ```
3236. **Check logs** for any errors:
324 ```bash
325 gordon logs
326 ```
327
328## Getting Help
329
330- [GitHub Issues](https://github.com/bnema/gordon/issues)
331- [Documentation](https://gordon.bnema.dev/docs)