v2.31.0-dev21 b59533b

running-in-container.md RawHistoryFolder

Running Gordon in a Container

Run Gordon itself inside a Docker or Podman container for isolated deployments.

What You'll Learn

  • Running Gordon in Docker with socket access
  • Running Gordon in Podman (rootless)
  • Configuration via environment variables
  • Volume management for persistence

Prerequisites

  • Docker or Podman installed on the host
  • Domain name pointing to your server
  • Basic understanding of container networking

Why Run Gordon in a Container?

  • Isolation: Gordon runs in its own environment
  • Portability: Same setup across different hosts
  • Updates: Easy version management with image tags
  • Orchestration: Can be managed by Docker Compose, Kubernetes, etc.

Docker

Quick Start

docker run -d \
  --name gordon \
  -p 80:8080 \
  -p 5000:5000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
  ghcr.io/bnema/gordon:latest

With Configuration File

Create gordon.toml:

[server]
port = 8080
registry_port = 5000
gordon_domain = "gordon.mydomain.com"

[routes]
"app.mydomain.com" = "myapp:latest"
"api.mydomain.com" = "myapi:latest"

[logging]
level = "info"

Run with mounted config:

docker run -d \
  --name gordon \
  -p 80:8080 \
  -p 5000:5000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
  ghcr.io/bnema/gordon:latest

Docker Compose

services:
  gordon:
    image: ghcr.io/bnema/gordon:latest
    container_name: gordon
    restart: unless-stopped
    ports:
      - "80:8080"
      - "5000:5000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - gordon-data:/data
      - ./gordon.toml:/etc/gordon/gordon.toml:ro
    environment:
      - GORDON_LOGGING_LEVEL=info
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  gordon-data:

Start with:

docker compose up -d

Podman

Socket Setup

Podman uses a different socket path. Enable the Podman socket first:

# For rootless Podman (user service)
systemctl --user enable --now podman.socket

# Socket path: /run/user/$(id -u)/podman/podman.sock

For system-wide (root) Podman:

sudo systemctl enable --now podman.socket

# Socket path: /run/podman/podman.sock

Quick Start (Rootless)

podman run -d \
  --name gordon \
  -p 8080:8080 \
  -p 5000:5000 \
  --security-opt label=disable \
  -v /run/user/$(id -u)/podman/podman.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
  -e DOCKER_HOST=unix:///var/run/docker.sock \
  ghcr.io/bnema/gordon:latest

Note: --security-opt label=disable is required for socket access with SELinux.

Quick Start (Root/System)

sudo podman run -d \
  --name gordon \
  -p 80:8080 \
  -p 5000:5000 \
  --security-opt label=disable \
  -v /run/podman/podman.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
  -e DOCKER_HOST=unix:///var/run/docker.sock \
  ghcr.io/bnema/gordon:latest

Podman Compose (Quadlet)

Create ~/.config/containers/systemd/gordon.container:

[Unit]
Description=Gordon Container Platform
After=podman.socket

[Container]
Image=ghcr.io/bnema/gordon:latest
ContainerName=gordon
PublishPort=8080:8080
PublishPort=5000:5000
Volume=/run/user/%U/podman/podman.sock:/var/run/docker.sock
Volume=gordon-data:/data
Environment=GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com
Environment=DOCKER_HOST=unix:///var/run/docker.sock
SecurityLabelDisable=true

[Service]
Restart=always

[Install]
WantedBy=default.target

Enable and start:

systemctl --user daemon-reload
systemctl --user enable --now gordon

Configuration Reference

Required Volumes

Mount Purpose
/var/run/docker.sock Access to container runtime (Docker or Podman socket)
/data Registry storage, logs, environment files

Optional Volumes

Mount Purpose
/etc/gordon/gordon.toml Configuration file

Ports

Port Purpose
8080 HTTP proxy (map to 80 on host)
5000 Container registry

Environment Variables

All configuration options can be set via environment variables with GORDON_ prefix:

Variable Description Example
GORDON_SERVER_PORT Proxy port 8080
GORDON_SERVER_REGISTRY_PORT Registry port 5000
GORDON_SERVER_REGISTRY_DOMAIN Registry domain (required) registry.mydomain.com
GORDON_SERVER_DATA_DIR Data directory /data
GORDON_LOGGING_LEVEL Log level debug, info, warn, error
GORDON_AUTH_ENABLED Enable authentication true
GORDON_AUTH_USERNAME Username for password auth admin

Note: For registry passwords, using plain password is deprecated. Use password_hash with a secrets backend instead. See Authentication for secure setup.

Using Pass Secrets Backend

To use the pass secrets backend in a container, you need to mount GPG keys and the password store.

Prerequisites on Host

# Install pass and initialize (if not already done)
sudo apt install pass gnupg
gpg --gen-key
pass init <your-gpg-key-id>

# Store Gordon secrets
pass insert gordon/auth/password_hash
pass insert gordon/auth/token_secret

Custom Dockerfile

The default Gordon image doesn't include pass. Create a custom Dockerfile:

FROM ghcr.io/bnema/gordon:latest

USER root
RUN apk add --no-cache pass gnupg
USER gordon

Build it:

docker build -t gordon-with-pass .

Docker Run with Pass

docker run -d \
  --name gordon \
  -p 80:8080 \
  -p 5000:5000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -v $HOME/.gnupg:/home/gordon/.gnupg:ro \
  -v $HOME/.password-store:/home/gordon/.password-store:ro \
  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
  gordon-with-pass

Docker Compose with Pass

services:
  gordon:
    build:
      context: .
      dockerfile: Dockerfile.gordon
    container_name: gordon
    restart: unless-stopped
    ports:
      - "80:8080"
      - "5000:5000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - gordon-data:/data
      - ./gordon.toml:/etc/gordon/gordon.toml:ro
      # GPG and pass mounts
      - ~/.gnupg:/home/gordon/.gnupg:ro
      - ~/.password-store:/home/gordon/.password-store:ro
    environment:
      - GORDON_SECRETS_BACKEND=pass

volumes:
  gordon-data:

Configuration

[auth]
enabled = true
secrets_backend = "pass"
token_secret = "gordon/auth/token_secret"
# Optional: enable password auth for interactive login
# username = "admin"
# password_hash = "gordon/auth/password_hash"

GPG Agent (Optional)

For GPG keys with passphrases, you may need to forward the GPG agent socket:

-v $(gpgconf --list-dirs agent-socket):/home/gordon/.gnupg/S.gpg-agent:ro

Or use a passphrase-less GPG key for automated deployments.

Troubleshooting Pass

"gpg: decryption failed: No secret key"

The GPG private key isn't accessible. Verify mounts:

docker exec gordon gpg --list-secret-keys

"pass: command not found"

Use the custom Dockerfile with pass installed.

Permission denied on .gnupg

Ensure the gordon user (UID 1000) can read the mounted directories:

# On host, check permissions
ls -la ~/.gnupg
ls -la ~/.password-store

Security Considerations

Container Port Binding

When Gordon manages containers, it binds their ports to 127.0.0.1 (localhost only) by default. This prevents direct network access to containers, forcing all traffic through Gordon's reverse proxy where:

  • Authentication is enforced
  • Rate limiting is applied
  • Security headers are added
  • Request logging is performed

When running Gordon itself in a container, it communicates with managed containers via Docker's internal network (no port publishing needed).

Docker Socket Access

Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications:

  • Gordon can create, stop, and remove any container
  • Gordon can access volumes and networks
  • Consider network policies to restrict Gordon's container access

Recommendations

  1. Use a dedicated host: Run Gordon on a dedicated server or VM
  2. Limit network exposure: Only expose ports 80/443 publicly
  3. Use TLS: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx)
  4. Enable authentication: Always enable authentication for the registry
  5. Container isolation: Container ports bound to localhost prevent direct access

Troubleshooting

"Cannot connect to Docker daemon"

Verify socket mount:

# Docker
docker exec gordon ls -la /var/run/docker.sock

# Podman - check socket exists
ls -la /run/user/$(id -u)/podman/podman.sock

"Permission denied" on socket

For Podman, ensure --security-opt label=disable is set.

For Docker, ensure the gordon user inside the container can access the socket:

# Check socket permissions on host
ls -la /var/run/docker.sock

Container can't bind to port 80

The Gordon container runs as non-root user gordon. Use port 8080 inside the container and map to 80 on the host:

-p 80:8080  # Host port 80 -> Container port 8080

Health check failing

# Check Gordon logs
docker logs gordon

# Test health endpoint manually
docker exec gordon wget -q -O- http://localhost:8080/health

Next Steps

source
1# Running Gordon in a Container
2
3Run Gordon itself inside a Docker or Podman container for isolated deployments.
4
5## What You'll Learn
6
7- Running Gordon in Docker with socket access
8- Running Gordon in Podman (rootless)
9- Configuration via environment variables
10- Volume management for persistence
11
12## Prerequisites
13
14- Docker or Podman installed on the host
15- Domain name pointing to your server
16- Basic understanding of container networking
17
18## Why Run Gordon in a Container?
19
20- **Isolation**: Gordon runs in its own environment
21- **Portability**: Same setup across different hosts
22- **Updates**: Easy version management with image tags
23- **Orchestration**: Can be managed by Docker Compose, Kubernetes, etc.
24
25## Docker
26
27### Quick Start
28
29```bash
30docker run -d \
31  --name gordon \
32  -p 80:8080 \
33  -p 5000:5000 \
34  -v /var/run/docker.sock:/var/run/docker.sock \
35  -v gordon-data:/data \
36  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
37  ghcr.io/bnema/gordon:latest
38```
39
40### With Configuration File
41
42Create `gordon.toml`:
43
44```toml
45[server]
46port = 8080
47registry_port = 5000
48gordon_domain = "gordon.mydomain.com"
49
50[routes]
51"app.mydomain.com" = "myapp:latest"
52"api.mydomain.com" = "myapi:latest"
53
54[logging]
55level = "info"
56```
57
58Run with mounted config:
59
60```bash
61docker run -d \
62  --name gordon \
63  -p 80:8080 \
64  -p 5000:5000 \
65  -v /var/run/docker.sock:/var/run/docker.sock \
66  -v gordon-data:/data \
67  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
68  ghcr.io/bnema/gordon:latest
69```
70
71### Docker Compose
72
73```yaml
74services:
75  gordon:
76    image: ghcr.io/bnema/gordon:latest
77    container_name: gordon
78    restart: unless-stopped
79    ports:
80      - "80:8080"
81      - "5000:5000"
82    volumes:
83      - /var/run/docker.sock:/var/run/docker.sock
84      - gordon-data:/data
85      - ./gordon.toml:/etc/gordon/gordon.toml:ro
86    environment:
87      - GORDON_LOGGING_LEVEL=info
88    healthcheck:
89      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
90      interval: 30s
91      timeout: 10s
92      retries: 3
93      start_period: 40s
94
95volumes:
96  gordon-data:
97```
98
99Start with:
100
101```bash
102docker compose up -d
103```
104
105## Podman
106
107### Socket Setup
108
109Podman uses a different socket path. Enable the Podman socket first:
110
111```bash
112# For rootless Podman (user service)
113systemctl --user enable --now podman.socket
114
115# Socket path: /run/user/$(id -u)/podman/podman.sock
116```
117
118For system-wide (root) Podman:
119
120```bash
121sudo systemctl enable --now podman.socket
122
123# Socket path: /run/podman/podman.sock
124```
125
126### Quick Start (Rootless)
127
128```bash
129podman run -d \
130  --name gordon \
131  -p 8080:8080 \
132  -p 5000:5000 \
133  --security-opt label=disable \
134  -v /run/user/$(id -u)/podman/podman.sock:/var/run/docker.sock \
135  -v gordon-data:/data \
136  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
137  -e DOCKER_HOST=unix:///var/run/docker.sock \
138  ghcr.io/bnema/gordon:latest
139```
140
141> **Note**: `--security-opt label=disable` is required for socket access with SELinux.
142
143### Quick Start (Root/System)
144
145```bash
146sudo podman run -d \
147  --name gordon \
148  -p 80:8080 \
149  -p 5000:5000 \
150  --security-opt label=disable \
151  -v /run/podman/podman.sock:/var/run/docker.sock \
152  -v gordon-data:/data \
153  -e GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com \
154  -e DOCKER_HOST=unix:///var/run/docker.sock \
155  ghcr.io/bnema/gordon:latest
156```
157
158### Podman Compose (Quadlet)
159
160Create `~/.config/containers/systemd/gordon.container`:
161
162```ini
163[Unit]
164Description=Gordon Container Platform
165After=podman.socket
166
167[Container]
168Image=ghcr.io/bnema/gordon:latest
169ContainerName=gordon
170PublishPort=8080:8080
171PublishPort=5000:5000
172Volume=/run/user/%U/podman/podman.sock:/var/run/docker.sock
173Volume=gordon-data:/data
174Environment=GORDON_SERVER_REGISTRY_DOMAIN=registry.mydomain.com
175Environment=DOCKER_HOST=unix:///var/run/docker.sock
176SecurityLabelDisable=true
177
178[Service]
179Restart=always
180
181[Install]
182WantedBy=default.target
183```
184
185Enable and start:
186
187```bash
188systemctl --user daemon-reload
189systemctl --user enable --now gordon
190```
191
192## Configuration Reference
193
194### Required Volumes
195
196| Mount | Purpose |
197|-------|---------|
198| `/var/run/docker.sock` | Access to container runtime (Docker or Podman socket) |
199| `/data` | Registry storage, logs, environment files |
200
201### Optional Volumes
202
203| Mount | Purpose |
204|-------|---------|
205| `/etc/gordon/gordon.toml` | Configuration file |
206
207### Ports
208
209| Port | Purpose |
210|------|---------|
211| `8080` | HTTP proxy (map to 80 on host) |
212| `5000` | Container registry |
213
214### Environment Variables
215
216All configuration options can be set via environment variables with `GORDON_` prefix:
217
218| Variable | Description | Example |
219|----------|-------------|---------|
220| `GORDON_SERVER_PORT` | Proxy port | `8080` |
221| `GORDON_SERVER_REGISTRY_PORT` | Registry port | `5000` |
222| `GORDON_SERVER_REGISTRY_DOMAIN` | Registry domain (required) | `registry.mydomain.com` |
223| `GORDON_SERVER_DATA_DIR` | Data directory | `/data` |
224| `GORDON_LOGGING_LEVEL` | Log level | `debug`, `info`, `warn`, `error` |
225| `GORDON_AUTH_ENABLED` | Enable authentication | `true` |
226| `GORDON_AUTH_USERNAME` | Username for password auth | `admin` |
227
228> **Note**: For registry passwords, using plain `password` is deprecated. Use `password_hash` with a secrets backend instead. See [Authentication](/docs/config/auth.md) for secure setup.
229
230## Using Pass Secrets Backend
231
232To use the `pass` secrets backend in a container, you need to mount GPG keys and the password store.
233
234### Prerequisites on Host
235
236```bash
237# Install pass and initialize (if not already done)
238sudo apt install pass gnupg
239gpg --gen-key
240pass init <your-gpg-key-id>
241
242# Store Gordon secrets
243pass insert gordon/auth/password_hash
244pass insert gordon/auth/token_secret
245```
246
247### Custom Dockerfile
248
249The default Gordon image doesn't include `pass`. Create a custom Dockerfile:
250
251```dockerfile
252FROM ghcr.io/bnema/gordon:latest
253
254USER root
255RUN apk add --no-cache pass gnupg
256USER gordon
257```
258
259Build it:
260
261```bash
262docker build -t gordon-with-pass .
263```
264
265### Docker Run with Pass
266
267```bash
268docker run -d \
269  --name gordon \
270  -p 80:8080 \
271  -p 5000:5000 \
272  -v /var/run/docker.sock:/var/run/docker.sock \
273  -v gordon-data:/data \
274  -v $HOME/.gnupg:/home/gordon/.gnupg:ro \
275  -v $HOME/.password-store:/home/gordon/.password-store:ro \
276  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
277  gordon-with-pass
278```
279
280### Docker Compose with Pass
281
282```yaml
283services:
284  gordon:
285    build:
286      context: .
287      dockerfile: Dockerfile.gordon
288    container_name: gordon
289    restart: unless-stopped
290    ports:
291      - "80:8080"
292      - "5000:5000"
293    volumes:
294      - /var/run/docker.sock:/var/run/docker.sock
295      - gordon-data:/data
296      - ./gordon.toml:/etc/gordon/gordon.toml:ro
297      # GPG and pass mounts
298      - ~/.gnupg:/home/gordon/.gnupg:ro
299      - ~/.password-store:/home/gordon/.password-store:ro
300    environment:
301      - GORDON_SECRETS_BACKEND=pass
302
303volumes:
304  gordon-data:
305```
306
307### Configuration
308
309```toml
310[auth]
311enabled = true
312secrets_backend = "pass"
313token_secret = "gordon/auth/token_secret"
314# Optional: enable password auth for interactive login
315# username = "admin"
316# password_hash = "gordon/auth/password_hash"
317```
318
319### GPG Agent (Optional)
320
321For GPG keys with passphrases, you may need to forward the GPG agent socket:
322
323```bash
324-v $(gpgconf --list-dirs agent-socket):/home/gordon/.gnupg/S.gpg-agent:ro
325```
326
327Or use a passphrase-less GPG key for automated deployments.
328
329### Troubleshooting Pass
330
331**"gpg: decryption failed: No secret key"**
332
333The GPG private key isn't accessible. Verify mounts:
334
335```bash
336docker exec gordon gpg --list-secret-keys
337```
338
339**"pass: command not found"**
340
341Use the custom Dockerfile with pass installed.
342
343**Permission denied on .gnupg**
344
345Ensure the gordon user (UID 1000) can read the mounted directories:
346
347```bash
348# On host, check permissions
349ls -la ~/.gnupg
350ls -la ~/.password-store
351```
352
353## Security Considerations
354
355### Container Port Binding
356
357When Gordon manages containers, it binds their ports to `127.0.0.1` (localhost only) by default. This prevents direct network access to containers, forcing all traffic through Gordon's reverse proxy where:
358
359- Authentication is enforced
360- Rate limiting is applied
361- Security headers are added
362- Request logging is performed
363
364When running Gordon itself in a container, it communicates with managed containers via Docker's internal network (no port publishing needed).
365
366### Docker Socket Access
367
368Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications:
369
370- Gordon can create, stop, and remove any container
371- Gordon can access volumes and networks
372- Consider network policies to restrict Gordon's container access
373
374### Recommendations
375
3761. **Use a dedicated host**: Run Gordon on a dedicated server or VM
3772. **Limit network exposure**: Only expose ports 80/443 publicly
3783. **Use TLS**: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx)
3794. **Enable authentication**: Always enable authentication for the registry
3805. **Container isolation**: Container ports bound to localhost prevent direct access
381
382## Troubleshooting
383
384### "Cannot connect to Docker daemon"
385
386Verify socket mount:
387
388```bash
389# Docker
390docker exec gordon ls -la /var/run/docker.sock
391
392# Podman - check socket exists
393ls -la /run/user/$(id -u)/podman/podman.sock
394```
395
396### "Permission denied" on socket
397
398For Podman, ensure `--security-opt label=disable` is set.
399
400For Docker, ensure the `gordon` user inside the container can access the socket:
401
402```bash
403# Check socket permissions on host
404ls -la /var/run/docker.sock
405```
406
407### Container can't bind to port 80
408
409The Gordon container runs as non-root user `gordon`. Use port 8080 inside the container and map to 80 on the host:
410
411```bash
412-p 80:8080  # Host port 80 -> Container port 8080
413```
414
415### Health check failing
416
417```bash
418# Check Gordon logs
419docker logs gordon
420
421# Test health endpoint manually
422docker exec gordon wget -q -O- http://localhost:8080/health
423```
424
425## Next Steps
426
427- [Configure routes](/docs/config/routes.md)
428- [Set up network groups](/docs/config/network-groups.md)
429- [Enable authentication](/docs/config/auth.md)

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