detached 5dc1b42

refactor: canonicalize gordon.proxy.port, deprecate gordon.port

gordon.proxy.port is more explicit about its purpose. Flip precedence
so gordon.proxy.port is checked first everywhere, keep gordon.port as
a deprecated alias for backward compatibility, and update all docs
and tests to use gordon.proxy.port exclusively.
commit
5dc1b42b651dc8a3933fb3efd7936beba8c61606
author
brice · · 2026-03-15 08:20 UTC
parent
c672160
signature
unsigned
11 files · +61 -59
M docs/config/auto-route.md +5 -5
@@ -175,7 +175,7 @@ The preferred method for automatic routing uses Dockerfile labels. This gives yo
175175 | Label | Description | Example |
176176 |-------|-------------|---------|
177177 | `gordon.domains` | Comma-separated list of domains | `app.example.com,www.app.example.com` |
178-| `gordon.port` | Container port to proxy | `3000` |
178+| `gordon.proxy.port` | Container port to proxy | `3000` |
179179 | `gordon.health` | Health check endpoint path | `/health` |
180180 | `gordon.env-file` | Path to .env file in image | `/app/.env.example` |
181181
@@ -189,7 +189,7 @@ RUN npm install
189189
190190 # Gordon routing labels
191191 LABEL gordon.domains="myapp.example.com,www.myapp.example.com"
192-LABEL gordon.port="3000"
192+LABEL gordon.proxy.port="3000"
193193 LABEL gordon.health="/api/health"
194194 LABEL gordon.env-file="/app/.env.example"
195195
@@ -203,7 +203,7 @@ When you push an image with Gordon labels:
203203
204204 1. Gordon reads the `gordon.domains` label
205205 2. Creates routes for each domain automatically
206-3. Uses `gordon.port` for the proxy target (or first EXPOSE port)
206+3. Uses `gordon.proxy.port` for the proxy target (or first EXPOSE port)
207207 4. Configures health checks if `gordon.health` is set
208208 5. Extracts environment variables from `gordon.env-file` if specified
209209
@@ -241,7 +241,7 @@ Gordon will:
241241
242242 ### Best Practices
243243
244-1. **Always set `gordon.port`** - Don't rely on EXPOSE detection
244+1. **Always set `gordon.proxy.port`** - Don't rely on EXPOSE detection
245245 2. **Use `gordon.health`** - Enables reliable deployment verification
246246 3. **Include `.env.example`** - Self-documenting environment requirements
247247 4. **Separate domains with commas** - No spaces around commas
@@ -261,7 +261,7 @@ COPY .env.example .
261261
262262 # Gordon labels
263263 LABEL gordon.domains="api.mycompany.com"
264-LABEL gordon.port="8080"
264+LABEL gordon.proxy.port="8080"
265265 LABEL gordon.health="/health"
266266 LABEL gordon.env-file="/app/.env.example"
267267
@@ -175,7 +175,7 @@ The preferred method for automatic routing uses Dockerfile labels. This gives yo
175 | Label | Description | Example |175 | Label | Description | Example |
176 |-------|-------------|---------|176 |-------|-------------|---------|
177 | `gordon.domains` | Comma-separated list of domains | `app.example.com,www.app.example.com` |177 | `gordon.domains` | Comma-separated list of domains | `app.example.com,www.app.example.com` |
178-| `gordon.port` | Container port to proxy | `3000` |178+| `gordon.proxy.port` | Container port to proxy | `3000` |
179 | `gordon.health` | Health check endpoint path | `/health` |179 | `gordon.health` | Health check endpoint path | `/health` |
180 | `gordon.env-file` | Path to .env file in image | `/app/.env.example` |180 | `gordon.env-file` | Path to .env file in image | `/app/.env.example` |
181 181
@@ -189,7 +189,7 @@ RUN npm install
189 189
190 # Gordon routing labels190 # Gordon routing labels
191 LABEL gordon.domains="myapp.example.com,www.myapp.example.com"191 LABEL gordon.domains="myapp.example.com,www.myapp.example.com"
192-LABEL gordon.port="3000"192+LABEL gordon.proxy.port="3000"
193 LABEL gordon.health="/api/health"193 LABEL gordon.health="/api/health"
194 LABEL gordon.env-file="/app/.env.example"194 LABEL gordon.env-file="/app/.env.example"
195 195
@@ -203,7 +203,7 @@ When you push an image with Gordon labels:
203 203
204 1. Gordon reads the `gordon.domains` label204 1. Gordon reads the `gordon.domains` label
205 2. Creates routes for each domain automatically205 2. Creates routes for each domain automatically
206-3. Uses `gordon.port` for the proxy target (or first EXPOSE port)206+3. Uses `gordon.proxy.port` for the proxy target (or first EXPOSE port)
207 4. Configures health checks if `gordon.health` is set207 4. Configures health checks if `gordon.health` is set
208 5. Extracts environment variables from `gordon.env-file` if specified208 5. Extracts environment variables from `gordon.env-file` if specified
209 209
@@ -241,7 +241,7 @@ Gordon will:
241 241
242 ### Best Practices242 ### Best Practices
243 243
244-1. **Always set `gordon.port`** - Don't rely on EXPOSE detection244+1. **Always set `gordon.proxy.port`** - Don't rely on EXPOSE detection
245 2. **Use `gordon.health`** - Enables reliable deployment verification245 2. **Use `gordon.health`** - Enables reliable deployment verification
246 3. **Include `.env.example`** - Self-documenting environment requirements246 3. **Include `.env.example`** - Self-documenting environment requirements
247 4. **Separate domains with commas** - No spaces around commas247 4. **Separate domains with commas** - No spaces around commas
@@ -261,7 +261,7 @@ COPY .env.example .
261 261
262 # Gordon labels262 # Gordon labels
263 LABEL gordon.domains="api.mycompany.com"263 LABEL gordon.domains="api.mycompany.com"
264-LABEL gordon.port="8080"264+LABEL gordon.proxy.port="8080"
265 LABEL gordon.health="/health"265 LABEL gordon.health="/health"
266 LABEL gordon.env-file="/app/.env.example"266 LABEL gordon.env-file="/app/.env.example"
267 267
M docs/reference/docker-labels.md +1 -2
@@ -42,8 +42,7 @@ Labels you can set in your Dockerfile:
4242 | Label | Example | Description |
4343 |-------|---------|-------------|
4444 | `gordon.domains` | `"app.example.com,www.app.example.com"` | Comma-separated domains for auto-route |
45-| `gordon.port` | `"3000"` | Port to proxy HTTP traffic to |
46-| `gordon.proxy.port` | `"3000"` | Port to proxy HTTP traffic to (legacy alias for `gordon.port`) |
45+| `gordon.proxy.port` | `"3000"` | Port to proxy HTTP traffic to |
4746 | `gordon.health` | `"/healthz"` | HTTP health check endpoint path for readiness probing |
4847 | `gordon.env-file` | `"/app/.env.example"` | Path to env template file inside the image |
4948
@@ -42,8 +42,7 @@ Labels you can set in your Dockerfile:
42 | Label | Example | Description |42 | Label | Example | Description |
43 |-------|---------|-------------|43 |-------|---------|-------------|
44 | `gordon.domains` | `"app.example.com,www.app.example.com"` | Comma-separated domains for auto-route |44 | `gordon.domains` | `"app.example.com,www.app.example.com"` | Comma-separated domains for auto-route |
45-| `gordon.port` | `"3000"` | Port to proxy HTTP traffic to |45+| `gordon.proxy.port` | `"3000"` | Port to proxy HTTP traffic to |
46-| `gordon.proxy.port` | `"3000"` | Port to proxy HTTP traffic to (legacy alias for `gordon.port`) |
47 | `gordon.health` | `"/healthz"` | HTTP health check endpoint path for readiness probing |46 | `gordon.health` | `"/healthz"` | HTTP health check endpoint path for readiness probing |
48 | `gordon.env-file` | `"/app/.env.example"` | Path to env template file inside the image |47 | `gordon.env-file` | `"/app/.env.example"` | Path to env template file inside the image |
49 48
M internal/adapters/in/cli/push_test.go +11 -11
@@ -357,20 +357,20 @@ func TestParseDockerfileLabels(t *testing.T) {
357357 {
358358 name: "multiple labels",
359359 content: `LABEL gordon.domain="myapp.example.com"
360-LABEL gordon.port="8080"
360+LABEL gordon.proxy.port="8080"
361361 LABEL gordon.health="/health"`,
362362 expected: map[string]string{
363- "gordon.domain": "myapp.example.com",
364- "gordon.port": "8080",
365- "gordon.health": "/health",
363+ "gordon.domain": "myapp.example.com",
364+ "gordon.proxy.port": "8080",
365+ "gordon.health": "/health",
366366 },
367367 },
368368 {
369369 name: "multi-label on one line",
370- content: `LABEL gordon.domain="myapp.example.com" gordon.port="8080"`,
370+ content: `LABEL gordon.domain="myapp.example.com" gordon.proxy.port="8080"`,
371371 expected: map[string]string{
372- "gordon.domain": "myapp.example.com",
373- "gordon.port": "8080",
372+ "gordon.domain": "myapp.example.com",
373+ "gordon.proxy.port": "8080",
374374 },
375375 },
376376 {
@@ -471,13 +471,13 @@ func TestSplitLabelPairs(t *testing.T) {
471471 },
472472 {
473473 name: "two pairs",
474- content: `gordon.domain="test.com" gordon.port="8080"`,
475- expected: []string{`gordon.domain="test.com"`, `gordon.port="8080"`},
474+ content: `gordon.domain="test.com" gordon.proxy.port="8080"`,
475+ expected: []string{`gordon.domain="test.com"`, `gordon.proxy.port="8080"`},
476476 },
477477 {
478478 name: "unquoted",
479- content: `gordon.domain=test.com gordon.port=8080`,
480- expected: []string{`gordon.domain=test.com`, `gordon.port=8080`},
479+ content: `gordon.domain=test.com gordon.proxy.port=8080`,
480+ expected: []string{`gordon.domain=test.com`, `gordon.proxy.port=8080`},
481481 },
482482 {
483483 name: "quoted with spaces in value",
@@ -357,20 +357,20 @@ func TestParseDockerfileLabels(t *testing.T) {
357 {357 {
358 name: "multiple labels",358 name: "multiple labels",
359 content: `LABEL gordon.domain="myapp.example.com"359 content: `LABEL gordon.domain="myapp.example.com"
360-LABEL gordon.port="8080"360+LABEL gordon.proxy.port="8080"
361 LABEL gordon.health="/health"`,361 LABEL gordon.health="/health"`,
362 expected: map[string]string{362 expected: map[string]string{
363- "gordon.domain": "myapp.example.com",363+ "gordon.domain": "myapp.example.com",
364- "gordon.port": "8080",364+ "gordon.proxy.port": "8080",
365- "gordon.health": "/health",365+ "gordon.health": "/health",
366 },366 },
367 },367 },
368 {368 {
369 name: "multi-label on one line",369 name: "multi-label on one line",
370- content: `LABEL gordon.domain="myapp.example.com" gordon.port="8080"`,370+ content: `LABEL gordon.domain="myapp.example.com" gordon.proxy.port="8080"`,
371 expected: map[string]string{371 expected: map[string]string{
372- "gordon.domain": "myapp.example.com",372+ "gordon.domain": "myapp.example.com",
373- "gordon.port": "8080",373+ "gordon.proxy.port": "8080",
374 },374 },
375 },375 },
376 {376 {
@@ -471,13 +471,13 @@ func TestSplitLabelPairs(t *testing.T) {
471 },471 },
472 {472 {
473 name: "two pairs",473 name: "two pairs",
474- content: `gordon.domain="test.com" gordon.port="8080"`,474+ content: `gordon.domain="test.com" gordon.proxy.port="8080"`,
475- expected: []string{`gordon.domain="test.com"`, `gordon.port="8080"`},475+ expected: []string{`gordon.domain="test.com"`, `gordon.proxy.port="8080"`},
476 },476 },
477 {477 {
478 name: "unquoted",478 name: "unquoted",
479- content: `gordon.domain=test.com gordon.port=8080`,479+ content: `gordon.domain=test.com gordon.proxy.port=8080`,
480- expected: []string{`gordon.domain=test.com`, `gordon.port=8080`},480+ expected: []string{`gordon.domain=test.com`, `gordon.proxy.port=8080`},
481 },481 },
482 {482 {
483 name: "quoted with spaces in value",483 name: "quoted with spaces in value",
M internal/domain/labels.go +3 -3
@@ -15,8 +15,7 @@ const (
1515 // exposing secret values.
1616 LabelEnvHash = "gordon.env-hash"
1717
18- // LabelProxyPort is a deprecated alias for LabelPort.
19- // Kept for backward compatibility; prefer LabelPort for new images.
18+ // LabelProxyPort specifies the container port to proxy HTTP traffic to.
2019 LabelProxyPort = "gordon.proxy.port"
2120
2221 // Auto-route image labels (for automatic route discovery)
@@ -24,7 +23,8 @@ const (
2423 LabelDomains = "gordon.domains"
2524 // LabelHealth specifies the health check endpoint path.
2625 LabelHealth = "gordon.health"
27- // LabelPort specifies the container port to proxy.
26+ // LabelPort is a deprecated alias for LabelProxyPort.
27+ // Kept for backward compatibility; prefer LabelProxyPort for new images.
2828 LabelPort = "gordon.port"
2929 // LabelEnvFile specifies the path to .env file inside the image.
3030 LabelEnvFile = "gordon.env-file"
@@ -15,8 +15,7 @@ const (
15 // exposing secret values.15 // exposing secret values.
16 LabelEnvHash = "gordon.env-hash"16 LabelEnvHash = "gordon.env-hash"
17 17
18- // LabelProxyPort is a deprecated alias for LabelPort.18+ // LabelProxyPort specifies the container port to proxy HTTP traffic to.
19- // Kept for backward compatibility; prefer LabelPort for new images.
20 LabelProxyPort = "gordon.proxy.port"19 LabelProxyPort = "gordon.proxy.port"
21 20
22 // Auto-route image labels (for automatic route discovery)21 // Auto-route image labels (for automatic route discovery)
@@ -24,7 +23,8 @@ const (
24 LabelDomains = "gordon.domains"23 LabelDomains = "gordon.domains"
25 // LabelHealth specifies the health check endpoint path.24 // LabelHealth specifies the health check endpoint path.
26 LabelHealth = "gordon.health"25 LabelHealth = "gordon.health"
27- // LabelPort specifies the container port to proxy.26+ // LabelPort is a deprecated alias for LabelProxyPort.
27+ // Kept for backward compatibility; prefer LabelProxyPort for new images.
28 LabelPort = "gordon.port"28 LabelPort = "gordon.port"
29 // LabelEnvFile specifies the path to .env file inside the image.29 // LabelEnvFile specifies the path to .env file inside the image.
30 LabelEnvFile = "gordon.env-file"30 LabelEnvFile = "gordon.env-file"
M internal/usecase/container/autoroute.go +5 -2
@@ -414,8 +414,11 @@ func parseImageLabels(configData []byte) (*domain.ImageLabels, error) {
414414 labels.Health = strings.TrimSpace(v)
415415 }
416416
417- if v, ok := config.Config.Labels[domain.LabelPort]; ok {
418- labels.Port = strings.TrimSpace(v)
417+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
418+ if v, ok := config.Config.Labels[key]; ok {
419+ labels.Port = strings.TrimSpace(v)
420+ break
421+ }
419422 }
420423
421424 if v, ok := config.Config.Labels[domain.LabelEnvFile]; ok {
@@ -414,8 +414,11 @@ func parseImageLabels(configData []byte) (*domain.ImageLabels, error) {
414 labels.Health = strings.TrimSpace(v)414 labels.Health = strings.TrimSpace(v)
415 }415 }
416 416
417- if v, ok := config.Config.Labels[domain.LabelPort]; ok {417+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
418- labels.Port = strings.TrimSpace(v)418+ if v, ok := config.Config.Labels[key]; ok {
419+ labels.Port = strings.TrimSpace(v)
420+ break
421+ }
419 }422 }
420 423
421 if v, ok := config.Config.Labels[domain.LabelEnvFile]; ok {424 if v, ok := config.Config.Labels[domain.LabelEnvFile]; ok {
M internal/usecase/container/autoroute_test.go +4 -4
@@ -263,10 +263,10 @@ func TestParseImageLabels(t *testing.T) {
263263 config: map[string]any{
264264 "config": map[string]any{
265265 "Labels": map[string]any{
266- "gordon.domain": "app.example.com",
267- "gordon.health": "/healthz",
268- "gordon.port": "8080",
269- "gordon.env-file": ".env.prod",
266+ "gordon.domain": "app.example.com",
267+ "gordon.health": "/healthz",
268+ "gordon.proxy.port": "8080",
269+ "gordon.env-file": ".env.prod",
270270 },
271271 },
272272 },
@@ -263,10 +263,10 @@ func TestParseImageLabels(t *testing.T) {
263 config: map[string]any{263 config: map[string]any{
264 "config": map[string]any{264 "config": map[string]any{
265 "Labels": map[string]any{265 "Labels": map[string]any{
266- "gordon.domain": "app.example.com",266+ "gordon.domain": "app.example.com",
267- "gordon.health": "/healthz",267+ "gordon.health": "/healthz",
268- "gordon.port": "8080",268+ "gordon.proxy.port": "8080",
269- "gordon.env-file": ".env.prod",269+ "gordon.env-file": ".env.prod",
270 },270 },
271 },271 },
272 },272 },
M internal/usecase/container/service.go +1 -1
@@ -2555,7 +2555,7 @@ func (s *Service) resolveProbeEndpoint(ctx context.Context, containerID string,
25552555 internalPort := 0
25562556 if containerConfig != nil {
25572557 if labels := containerConfig.Labels; labels != nil {
2558- for _, key := range []string{domain.LabelPort, domain.LabelProxyPort} {
2558+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
25592559 if portStr, ok := labels[key]; ok && portStr != "" {
25602560 port, err := strconv.Atoi(portStr)
25612561 if err == nil && port > 0 && port <= 65535 {
@@ -2555,7 +2555,7 @@ func (s *Service) resolveProbeEndpoint(ctx context.Context, containerID string,
2555 internalPort := 02555 internalPort := 0
2556 if containerConfig != nil {2556 if containerConfig != nil {
2557 if labels := containerConfig.Labels; labels != nil {2557 if labels := containerConfig.Labels; labels != nil {
2558- for _, key := range []string{domain.LabelPort, domain.LabelProxyPort} {2558+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
2559 if portStr, ok := labels[key]; ok && portStr != "" {2559 if portStr, ok := labels[key]; ok && portStr != "" {
2560 port, err := strconv.Atoi(portStr)2560 port, err := strconv.Atoi(portStr)
2561 if err == nil && port > 0 && port <= 65535 {2561 if err == nil && port > 0 && port <= 65535 {
M internal/usecase/container/service_readiness_test.go +11 -11
@@ -217,7 +217,7 @@ func TestService_WaitForHealthy_EmptyStatusIsReportedAsStarting(t *testing.T) {
217217 assert.True(t, strings.Contains(err.Error(), "last status: starting"))
218218 }
219219
220-func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {
220+func TestService_WaitForReady_ProbeUsesGordonProxyPortLabel(t *testing.T) {
221221 runtime := mocks.NewMockContainerRuntime(t)
222222 svc := NewService(runtime, nil, nil, nil, Config{
223223 ReadinessMode: "auto",
@@ -231,11 +231,11 @@ func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {
231231 probeIP, probePort := reserveEphemeralAddr(t)
232232 _ = probeIP
233233
234- // Container config has gordon.port label — should be used for probe endpoint
234+ // Container config has gordon.proxy.port label — should be used for probe endpoint
235235 containerConfig := &domain.ContainerConfig{
236236 Ports: []int{22, 3000},
237237 Labels: map[string]string{
238- domain.LabelPort: "3000",
238+ domain.LabelProxyPort: "3000",
239239 },
240240 }
241241
@@ -250,7 +250,7 @@ func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {
250250 assert.Contains(t, err.Error(), "HTTP alive probe timeout")
251251 }
252252
253-func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {
253+func TestService_WaitForReady_ProbeUsesDeprecatedPortLabel(t *testing.T) {
254254 runtime := mocks.NewMockContainerRuntime(t)
255255 svc := NewService(runtime, nil, nil, nil, Config{
256256 ReadinessMode: "auto",
@@ -264,11 +264,11 @@ func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {
264264 probeIP, probePort := reserveEphemeralAddr(t)
265265 _ = probeIP // probeIP not used directly but port is
266266
267- // Container config has deprecated gordon.proxy.port — should still work
267+ // Container config has deprecated gordon.port — should still work
268268 containerConfig := &domain.ContainerConfig{
269269 Ports: []int{22, 3000},
270270 Labels: map[string]string{
271- domain.LabelProxyPort: "3000",
271+ domain.LabelPort: "3000",
272272 },
273273 }
274274
@@ -281,7 +281,7 @@ func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {
281281 assert.Contains(t, err.Error(), "HTTP alive probe timeout")
282282 }
283283
284-func TestService_WaitForReady_GordonPortTakesPriorityOverProxyPort(t *testing.T) {
284+func TestService_WaitForReady_ProxyPortTakesPriorityOverPort(t *testing.T) {
285285 runtime := mocks.NewMockContainerRuntime(t)
286286 svc := NewService(runtime, nil, nil, nil, Config{
287287 ReadinessMode: "auto",
@@ -294,18 +294,18 @@ func TestService_WaitForReady_GordonPortTakesPriorityOverProxyPort(t *testing.T)
294294 probeIP, probePort := reserveEphemeralAddr(t)
295295 _ = probeIP
296296
297- // Both labels set — gordon.port (8080) should win over gordon.proxy.port (3000)
297+ // Both labels set — gordon.proxy.port (8080) should win over gordon.port (3000)
298298 containerConfig := &domain.ContainerConfig{
299299 Ports: []int{22, 3000, 8080},
300300 Labels: map[string]string{
301- domain.LabelPort: "8080",
302- domain.LabelProxyPort: "3000",
301+ domain.LabelProxyPort: "8080",
302+ domain.LabelPort: "3000",
303303 },
304304 }
305305
306306 runtime.EXPECT().IsContainerRunning(mock.Anything, containerID).Return(true, nil).Once()
307307 runtime.EXPECT().GetContainerHealthStatus(mock.Anything, containerID).Return("", false, nil).Once()
308- // Should use gordon.port=8080, NOT gordon.proxy.port=3000
308+ // Should use gordon.proxy.port=8080, NOT gordon.port=3000
309309 runtime.EXPECT().GetContainerPort(mock.Anything, containerID, 8080).Return(probePort, nil).Once()
310310
311311 err := svc.waitForReady(ctx, containerID, containerConfig)
@@ -217,7 +217,7 @@ func TestService_WaitForHealthy_EmptyStatusIsReportedAsStarting(t *testing.T) {
217 assert.True(t, strings.Contains(err.Error(), "last status: starting"))217 assert.True(t, strings.Contains(err.Error(), "last status: starting"))
218 }218 }
219 219
220-func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {220+func TestService_WaitForReady_ProbeUsesGordonProxyPortLabel(t *testing.T) {
221 runtime := mocks.NewMockContainerRuntime(t)221 runtime := mocks.NewMockContainerRuntime(t)
222 svc := NewService(runtime, nil, nil, nil, Config{222 svc := NewService(runtime, nil, nil, nil, Config{
223 ReadinessMode: "auto",223 ReadinessMode: "auto",
@@ -231,11 +231,11 @@ func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {
231 probeIP, probePort := reserveEphemeralAddr(t)231 probeIP, probePort := reserveEphemeralAddr(t)
232 _ = probeIP232 _ = probeIP
233 233
234- // Container config has gordon.port label — should be used for probe endpoint234+ // Container config has gordon.proxy.port label — should be used for probe endpoint
235 containerConfig := &domain.ContainerConfig{235 containerConfig := &domain.ContainerConfig{
236 Ports: []int{22, 3000},236 Ports: []int{22, 3000},
237 Labels: map[string]string{237 Labels: map[string]string{
238- domain.LabelPort: "3000",238+ domain.LabelProxyPort: "3000",
239 },239 },
240 }240 }
241 241
@@ -250,7 +250,7 @@ func TestService_WaitForReady_ProbeUsesGordonPortLabel(t *testing.T) {
250 assert.Contains(t, err.Error(), "HTTP alive probe timeout")250 assert.Contains(t, err.Error(), "HTTP alive probe timeout")
251 }251 }
252 252
253-func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {253+func TestService_WaitForReady_ProbeUsesDeprecatedPortLabel(t *testing.T) {
254 runtime := mocks.NewMockContainerRuntime(t)254 runtime := mocks.NewMockContainerRuntime(t)
255 svc := NewService(runtime, nil, nil, nil, Config{255 svc := NewService(runtime, nil, nil, nil, Config{
256 ReadinessMode: "auto",256 ReadinessMode: "auto",
@@ -264,11 +264,11 @@ func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {
264 probeIP, probePort := reserveEphemeralAddr(t)264 probeIP, probePort := reserveEphemeralAddr(t)
265 _ = probeIP // probeIP not used directly but port is265 _ = probeIP // probeIP not used directly but port is
266 266
267- // Container config has deprecated gordon.proxy.port — should still work267+ // Container config has deprecated gordon.port — should still work
268 containerConfig := &domain.ContainerConfig{268 containerConfig := &domain.ContainerConfig{
269 Ports: []int{22, 3000},269 Ports: []int{22, 3000},
270 Labels: map[string]string{270 Labels: map[string]string{
271- domain.LabelProxyPort: "3000",271+ domain.LabelPort: "3000",
272 },272 },
273 }273 }
274 274
@@ -281,7 +281,7 @@ func TestService_WaitForReady_ProbeUsesDeprecatedProxyPortLabel(t *testing.T) {
281 assert.Contains(t, err.Error(), "HTTP alive probe timeout")281 assert.Contains(t, err.Error(), "HTTP alive probe timeout")
282 }282 }
283 283
284-func TestService_WaitForReady_GordonPortTakesPriorityOverProxyPort(t *testing.T) {284+func TestService_WaitForReady_ProxyPortTakesPriorityOverPort(t *testing.T) {
285 runtime := mocks.NewMockContainerRuntime(t)285 runtime := mocks.NewMockContainerRuntime(t)
286 svc := NewService(runtime, nil, nil, nil, Config{286 svc := NewService(runtime, nil, nil, nil, Config{
287 ReadinessMode: "auto",287 ReadinessMode: "auto",
@@ -294,18 +294,18 @@ func TestService_WaitForReady_GordonPortTakesPriorityOverProxyPort(t *testing.T)
294 probeIP, probePort := reserveEphemeralAddr(t)294 probeIP, probePort := reserveEphemeralAddr(t)
295 _ = probeIP295 _ = probeIP
296 296
297- // Both labels set — gordon.port (8080) should win over gordon.proxy.port (3000)297+ // Both labels set — gordon.proxy.port (8080) should win over gordon.port (3000)
298 containerConfig := &domain.ContainerConfig{298 containerConfig := &domain.ContainerConfig{
299 Ports: []int{22, 3000, 8080},299 Ports: []int{22, 3000, 8080},
300 Labels: map[string]string{300 Labels: map[string]string{
301- domain.LabelPort: "8080",301+ domain.LabelProxyPort: "8080",
302- domain.LabelProxyPort: "3000",302+ domain.LabelPort: "3000",
303 },303 },
304 }304 }
305 305
306 runtime.EXPECT().IsContainerRunning(mock.Anything, containerID).Return(true, nil).Once()306 runtime.EXPECT().IsContainerRunning(mock.Anything, containerID).Return(true, nil).Once()
307 runtime.EXPECT().GetContainerHealthStatus(mock.Anything, containerID).Return("", false, nil).Once()307 runtime.EXPECT().GetContainerHealthStatus(mock.Anything, containerID).Return("", false, nil).Once()
308- // Should use gordon.port=8080, NOT gordon.proxy.port=3000308+ // Should use gordon.proxy.port=8080, NOT gordon.port=3000
309 runtime.EXPECT().GetContainerPort(mock.Anything, containerID, 8080).Return(probePort, nil).Once()309 runtime.EXPECT().GetContainerPort(mock.Anything, containerID, 8080).Return(probePort, nil).Once()
310 310
311 err := svc.waitForReady(ctx, containerID, containerConfig)311 err := svc.waitForReady(ctx, containerID, containerConfig)
M internal/usecase/container/service_test.go +6 -6
@@ -3408,11 +3408,11 @@ func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) {
34083408 // Exposed ports — image exposes both SSH and HTTP
34093409 runtime.EXPECT().GetImageExposedPorts(mock.Anything, "gitea/gitea:latest").Return([]int{22, 3000}, nil)
34103410
3411- // Image labels include gordon.port and gordon.health
3411+ // Image labels include gordon.proxy.port and gordon.health
34123412 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{
3413- domain.LabelPort: "3000",
3414- domain.LabelHealth: "/healthz",
3415- "some.other.label": "ignored",
3413+ domain.LabelProxyPort: "3000",
3414+ domain.LabelHealth: "/healthz",
3415+ "some.other.label": "ignored",
34163416 }, nil)
34173417
34183418 // Load environment
@@ -3427,8 +3427,8 @@ func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) {
34273427 Status: "created",
34283428 }
34293429 runtime.EXPECT().CreateContainer(mock.Anything, mock.MatchedBy(func(cfg *domain.ContainerConfig) bool {
3430- // Verify gordon.port was propagated from image labels
3431- if cfg.Labels[domain.LabelPort] != "3000" {
3430+ // Verify gordon.proxy.port was propagated from image labels
3431+ if cfg.Labels[domain.LabelProxyPort] != "3000" {
34323432 return false
34333433 }
34343434 // Verify gordon.health was propagated from image labels
@@ -3408,11 +3408,11 @@ func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) {
3408 // Exposed ports — image exposes both SSH and HTTP3408 // Exposed ports — image exposes both SSH and HTTP
3409 runtime.EXPECT().GetImageExposedPorts(mock.Anything, "gitea/gitea:latest").Return([]int{22, 3000}, nil)3409 runtime.EXPECT().GetImageExposedPorts(mock.Anything, "gitea/gitea:latest").Return([]int{22, 3000}, nil)
3410 3410
3411- // Image labels include gordon.port and gordon.health3411+ // Image labels include gordon.proxy.port and gordon.health
3412 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{3412 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{
3413- domain.LabelPort: "3000",3413+ domain.LabelProxyPort: "3000",
3414- domain.LabelHealth: "/healthz",3414+ domain.LabelHealth: "/healthz",
3415- "some.other.label": "ignored",3415+ "some.other.label": "ignored",
3416 }, nil)3416 }, nil)
3417 3417
3418 // Load environment3418 // Load environment
@@ -3427,8 +3427,8 @@ func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) {
3427 Status: "created",3427 Status: "created",
3428 }3428 }
3429 runtime.EXPECT().CreateContainer(mock.Anything, mock.MatchedBy(func(cfg *domain.ContainerConfig) bool {3429 runtime.EXPECT().CreateContainer(mock.Anything, mock.MatchedBy(func(cfg *domain.ContainerConfig) bool {
3430- // Verify gordon.port was propagated from image labels3430+ // Verify gordon.proxy.port was propagated from image labels
3431- if cfg.Labels[domain.LabelPort] != "3000" {3431+ if cfg.Labels[domain.LabelProxyPort] != "3000" {
3432 return false3432 return false
3433 }3433 }
3434 // Verify gordon.health was propagated from image labels3434 // Verify gordon.health was propagated from image labels
M internal/usecase/proxy/service.go +3 -3
@@ -693,18 +693,18 @@ func (s *Service) isRunningInContainer() bool {
693693 }
694694
695695 // getProxyPort determines the port to proxy to for an image.
696-// It checks for gordon.port first, then the deprecated gordon.proxy.port alias, then falls back to the first exposed port.
696+// It checks for gordon.proxy.port first, then the deprecated gordon.port alias, then falls back to the first exposed port.
697697 func (s *Service) getProxyPort(ctx context.Context, imageRef string) (int, error) {
698698 log := zerowrap.FromCtx(ctx)
699699
700700 log.Debug().Str("image_ref", imageRef).Msg("determining proxy port for image")
701701
702- // Check for explicit port label (gordon.port preferred, gordon.proxy.port as deprecated alias)
702+ // Check for explicit port label (gordon.proxy.port preferred, gordon.port as deprecated alias)
703703 labels, err := s.runtime.GetImageLabels(ctx, imageRef)
704704 if err != nil {
705705 log.Debug().Err(err).Msg("failed to get image labels, falling back to exposed ports")
706706 } else {
707- for _, key := range []string{domain.LabelPort, domain.LabelProxyPort} {
707+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
708708 if portStr, ok := labels[key]; ok && portStr != "" {
709709 port, err := strconv.Atoi(portStr)
710710 if err == nil && port > 0 && port <= 65535 {
@@ -693,18 +693,18 @@ func (s *Service) isRunningInContainer() bool {
693 }693 }
694 694
695 // getProxyPort determines the port to proxy to for an image.695 // getProxyPort determines the port to proxy to for an image.
696-// It checks for gordon.port first, then the deprecated gordon.proxy.port alias, then falls back to the first exposed port.696+// It checks for gordon.proxy.port first, then the deprecated gordon.port alias, then falls back to the first exposed port.
697 func (s *Service) getProxyPort(ctx context.Context, imageRef string) (int, error) {697 func (s *Service) getProxyPort(ctx context.Context, imageRef string) (int, error) {
698 log := zerowrap.FromCtx(ctx)698 log := zerowrap.FromCtx(ctx)
699 699
700 log.Debug().Str("image_ref", imageRef).Msg("determining proxy port for image")700 log.Debug().Str("image_ref", imageRef).Msg("determining proxy port for image")
701 701
702- // Check for explicit port label (gordon.port preferred, gordon.proxy.port as deprecated alias)702+ // Check for explicit port label (gordon.proxy.port preferred, gordon.port as deprecated alias)
703 labels, err := s.runtime.GetImageLabels(ctx, imageRef)703 labels, err := s.runtime.GetImageLabels(ctx, imageRef)
704 if err != nil {704 if err != nil {
705 log.Debug().Err(err).Msg("failed to get image labels, falling back to exposed ports")705 log.Debug().Err(err).Msg("failed to get image labels, falling back to exposed ports")
706 } else {706 } else {
707- for _, key := range []string{domain.LabelPort, domain.LabelProxyPort} {707+ for _, key := range []string{domain.LabelProxyPort, domain.LabelPort} {
708 if portStr, ok := labels[key]; ok && portStr != "" {708 if portStr, ok := labels[key]; ok && portStr != "" {
709 port, err := strconv.Atoi(portStr)709 port, err := strconv.Atoi(portStr)
710 if err == nil && port > 0 && port <= 65535 {710 if err == nil && port > 0 && port <= 65535 {
M internal/usecase/proxy/service_test.go +11 -11
@@ -572,7 +572,7 @@ func TestDrainRegistryInFlightTimeout(t *testing.T) {
572572 }
573573 }
574574
575-func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {
575+func TestService_GetTarget_HostMode_UsesProxyPortLabel(t *testing.T) {
576576 runtime := outmocks.NewMockContainerRuntime(t)
577577 containerSvc := inmocks.NewMockContainerService(t)
578578 configSvc := inmocks.NewMockConfigService(t)
@@ -595,9 +595,9 @@ func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {
595595 {Domain: "git.example.com", Image: "gitea/gitea:latest"},
596596 })
597597
598- // Image has gordon.port=3000 label — should use 3000, not first exposed port
598+ // Image has gordon.proxy.port=3000 label
599599 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{
600- domain.LabelPort: "3000",
600+ domain.LabelProxyPort: "3000",
601601 }, nil)
602602
603603 // Host port mapping for internal port 3000
@@ -611,7 +611,7 @@ func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {
611611 assert.Equal(t, "c-gitea", result.ContainerID)
612612 }
613613
614-func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {
614+func TestService_GetTarget_HostMode_UsesDeprecatedPortLabel(t *testing.T) {
615615 runtime := outmocks.NewMockContainerRuntime(t)
616616 containerSvc := inmocks.NewMockContainerService(t)
617617 configSvc := inmocks.NewMockConfigService(t)
@@ -631,9 +631,9 @@ func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {
631631 {Domain: "app.example.com", Image: "myapp:latest"},
632632 })
633633
634- // Image has deprecated gordon.proxy.port label — should still work
634+ // Image has deprecated gordon.port label — should still work
635635 runtime.EXPECT().GetImageLabels(mock.Anything, "myapp:latest").Return(map[string]string{
636- domain.LabelProxyPort: "8080",
636+ domain.LabelPort: "8080",
637637 }, nil)
638638
639639 runtime.EXPECT().GetContainerPort(mock.Anything, "c-app", 8080).Return(33000, nil)
@@ -644,7 +644,7 @@ func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {
644644 assert.Equal(t, 33000, result.Port)
645645 }
646646
647-func TestService_GetTarget_HostMode_GordonPortWinsOverProxyPort(t *testing.T) {
647+func TestService_GetTarget_HostMode_ProxyPortWinsOverPort(t *testing.T) {
648648 runtime := outmocks.NewMockContainerRuntime(t)
649649 containerSvc := inmocks.NewMockContainerService(t)
650650 configSvc := inmocks.NewMockConfigService(t)
@@ -664,13 +664,13 @@ func TestService_GetTarget_HostMode_GordonPortWinsOverProxyPort(t *testing.T) {
664664 {Domain: "dual.example.com", Image: "dualapp:latest"},
665665 })
666666
667- // Both labels set — gordon.port=9000 should win over gordon.proxy.port=3000
667+ // Both labels set — gordon.proxy.port=9000 should win over gordon.port=3000
668668 runtime.EXPECT().GetImageLabels(mock.Anything, "dualapp:latest").Return(map[string]string{
669- domain.LabelPort: "9000",
670- domain.LabelProxyPort: "3000",
669+ domain.LabelProxyPort: "9000",
670+ domain.LabelPort: "3000",
671671 }, nil)
672672
673- // Should use 9000 (gordon.port), NOT 3000 (gordon.proxy.port)
673+ // Should use 9000 (gordon.proxy.port), NOT 3000 (gordon.port)
674674 runtime.EXPECT().GetContainerPort(mock.Anything, "c-dual", 9000).Return(34000, nil)
675675
676676 result, err := svc.GetTarget(ctx, "dual.example.com")
@@ -572,7 +572,7 @@ func TestDrainRegistryInFlightTimeout(t *testing.T) {
572 }572 }
573 }573 }
574 574
575-func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {575+func TestService_GetTarget_HostMode_UsesProxyPortLabel(t *testing.T) {
576 runtime := outmocks.NewMockContainerRuntime(t)576 runtime := outmocks.NewMockContainerRuntime(t)
577 containerSvc := inmocks.NewMockContainerService(t)577 containerSvc := inmocks.NewMockContainerService(t)
578 configSvc := inmocks.NewMockConfigService(t)578 configSvc := inmocks.NewMockConfigService(t)
@@ -595,9 +595,9 @@ func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {
595 {Domain: "git.example.com", Image: "gitea/gitea:latest"},595 {Domain: "git.example.com", Image: "gitea/gitea:latest"},
596 })596 })
597 597
598- // Image has gordon.port=3000 label — should use 3000, not first exposed port598+ // Image has gordon.proxy.port=3000 label
599 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{599 runtime.EXPECT().GetImageLabels(mock.Anything, "gitea/gitea:latest").Return(map[string]string{
600- domain.LabelPort: "3000",600+ domain.LabelProxyPort: "3000",
601 }, nil)601 }, nil)
602 602
603 // Host port mapping for internal port 3000603 // Host port mapping for internal port 3000
@@ -611,7 +611,7 @@ func TestService_GetTarget_HostMode_UsesGordonPortLabel(t *testing.T) {
611 assert.Equal(t, "c-gitea", result.ContainerID)611 assert.Equal(t, "c-gitea", result.ContainerID)
612 }612 }
613 613
614-func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {614+func TestService_GetTarget_HostMode_UsesDeprecatedPortLabel(t *testing.T) {
615 runtime := outmocks.NewMockContainerRuntime(t)615 runtime := outmocks.NewMockContainerRuntime(t)
616 containerSvc := inmocks.NewMockContainerService(t)616 containerSvc := inmocks.NewMockContainerService(t)
617 configSvc := inmocks.NewMockConfigService(t)617 configSvc := inmocks.NewMockConfigService(t)
@@ -631,9 +631,9 @@ func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {
631 {Domain: "app.example.com", Image: "myapp:latest"},631 {Domain: "app.example.com", Image: "myapp:latest"},
632 })632 })
633 633
634- // Image has deprecated gordon.proxy.port label — should still work634+ // Image has deprecated gordon.port label — should still work
635 runtime.EXPECT().GetImageLabels(mock.Anything, "myapp:latest").Return(map[string]string{635 runtime.EXPECT().GetImageLabels(mock.Anything, "myapp:latest").Return(map[string]string{
636- domain.LabelProxyPort: "8080",636+ domain.LabelPort: "8080",
637 }, nil)637 }, nil)
638 638
639 runtime.EXPECT().GetContainerPort(mock.Anything, "c-app", 8080).Return(33000, nil)639 runtime.EXPECT().GetContainerPort(mock.Anything, "c-app", 8080).Return(33000, nil)
@@ -644,7 +644,7 @@ func TestService_GetTarget_HostMode_UsesDeprecatedProxyPortLabel(t *testing.T) {
644 assert.Equal(t, 33000, result.Port)644 assert.Equal(t, 33000, result.Port)
645 }645 }
646 646
647-func TestService_GetTarget_HostMode_GordonPortWinsOverProxyPort(t *testing.T) {647+func TestService_GetTarget_HostMode_ProxyPortWinsOverPort(t *testing.T) {
648 runtime := outmocks.NewMockContainerRuntime(t)648 runtime := outmocks.NewMockContainerRuntime(t)
649 containerSvc := inmocks.NewMockContainerService(t)649 containerSvc := inmocks.NewMockContainerService(t)
650 configSvc := inmocks.NewMockConfigService(t)650 configSvc := inmocks.NewMockConfigService(t)
@@ -664,13 +664,13 @@ func TestService_GetTarget_HostMode_GordonPortWinsOverProxyPort(t *testing.T) {
664 {Domain: "dual.example.com", Image: "dualapp:latest"},664 {Domain: "dual.example.com", Image: "dualapp:latest"},
665 })665 })
666 666
667- // Both labels set — gordon.port=9000 should win over gordon.proxy.port=3000667+ // Both labels set — gordon.proxy.port=9000 should win over gordon.port=3000
668 runtime.EXPECT().GetImageLabels(mock.Anything, "dualapp:latest").Return(map[string]string{668 runtime.EXPECT().GetImageLabels(mock.Anything, "dualapp:latest").Return(map[string]string{
669- domain.LabelPort: "9000",669+ domain.LabelProxyPort: "9000",
670- domain.LabelProxyPort: "3000",670+ domain.LabelPort: "3000",
671 }, nil)671 }, nil)
672 672
673- // Should use 9000 (gordon.port), NOT 3000 (gordon.proxy.port)673+ // Should use 9000 (gordon.proxy.port), NOT 3000 (gordon.port)
674 runtime.EXPECT().GetContainerPort(mock.Anything, "c-dual", 9000).Return(34000, nil)674 runtime.EXPECT().GetContainerPort(mock.Anything, "c-dual", 9000).Return(34000, nil)
675 675
676 result, err := svc.GetTarget(ctx, "dual.example.com")676 result, err := svc.GetTarget(ctx, "dual.example.com")

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