v2.7.0 fac7705

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_REGISTRY_AUTH_ENABLED Enable registry auth true
GORDON_REGISTRY_AUTH_USERNAME Registry username admin

Note: For registry passwords, using plain password is deprecated. Use password_hash with a secrets backend instead. See Registry Auth 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

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 registry auth: Always enable authentication for the registry

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_REGISTRY_AUTH_ENABLED` | Enable registry auth | `true` |
226| `GORDON_REGISTRY_AUTH_USERNAME` | Registry username | `admin` |
227
228> **Note**: For registry passwords, using plain `password` is deprecated. Use `password_hash` with a secrets backend instead. See [Registry Auth](/docs/config/registry-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### Docker Socket Access
356
357Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications:
358
359- Gordon can create, stop, and remove any container
360- Gordon can access volumes and networks
361- Consider network policies to restrict Gordon's container access
362
363### Recommendations
364
3651. **Use a dedicated host**: Run Gordon on a dedicated server or VM
3662. **Limit network exposure**: Only expose ports 80/443 publicly
3673. **Use TLS**: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx)
3684. **Enable registry auth**: Always enable authentication for the registry
369
370## Troubleshooting
371
372### "Cannot connect to Docker daemon"
373
374Verify socket mount:
375
376```bash
377# Docker
378docker exec gordon ls -la /var/run/docker.sock
379
380# Podman - check socket exists
381ls -la /run/user/$(id -u)/podman/podman.sock
382```
383
384### "Permission denied" on socket
385
386For Podman, ensure `--security-opt label=disable` is set.
387
388For Docker, ensure the `gordon` user inside the container can access the socket:
389
390```bash
391# Check socket permissions on host
392ls -la /var/run/docker.sock
393```
394
395### Container can't bind to port 80
396
397The Gordon container runs as non-root user `gordon`. Use port 8080 inside the container and map to 80 on the host:
398
399```bash
400-p 80:8080  # Host port 80 -> Container port 8080
401```
402
403### Health check failing
404
405```bash
406# Check Gordon logs
407docker logs gordon
408
409# Test health endpoint manually
410docker exec gordon wget -q -O- http://localhost:8080/health
411```
412
413## Next Steps
414
415- [Configure routes](/docs/config/routes.md)
416- [Set up network groups](/docs/config/network-groups.md)
417- [Enable registry authentication](/docs/config/registry-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