detached 9e1b73a

docs: restructure documentation for gordon-webapp

- Add docs/ folder with configuration, CLI, and deployment reference
- Add wiki/ folder with tutorials, guides, and examples
- Simplify README to point to documentation website
- Remove examples/ folder (content moved to docs and wiki)

Documentation now lives at https://gordon.bnema.dev
commit
9e1b73a5891355b8b481ef0f02335efde71d3bd5
author
brice · · 2026-01-15 05:42 UTC
parent
311217f
signature
unsigned
45 files · +5957 -1470
M README.md +20 -336
@@ -5,12 +5,14 @@
55
66 Self-hosted web app deployments. Push to your registry, Gordon does the rest.
77
8-- Website: https://github.com/bnema/gordon
9-- Documentation: [Configuration](examples/) | [Setup Guide](#detailed-setup-guide-podman-rootless-mode)
8+- Website: https://gordon.bnema.dev
9+- Documentation: [Docs](https://gordon.bnema.dev/docs) | [Wiki](https://gordon.bnema.dev/wiki)
1010 - Discuss: [GitHub Discussions](https://github.com/bnema/gordon/discussions)
1111
1212 ---
1313
14+## What is Gordon?
15+
1416 Gordon is a private container registry + HTTP reverse proxy for your VPS. Push a container image exposing a web port, it deploys automatically with zero downtime.
1517
1618 ```bash
@@ -21,23 +23,24 @@ docker push registry.your-server.com/myapp:latest
2123
2224 Build on your machine, push to deploy. Works from your laptop or CI.
2325
24-**What it does:**
25-- Runs a private Docker registry on your VPS
26-- Routes domains to containers via HTTP reverse proxy
27-- Deploys automatically when you push a new image
28-- Zero downtime updates, persistent volumes, environment merging
26+**Features:**
27+- Private Docker registry on your VPS
28+- Domain-to-container routing via HTTP reverse proxy
29+- Automatic deployment on image push
30+- Zero downtime updates
31+- Persistent volumes from Dockerfile VOLUME directives
32+- Environment variable management with secrets support
33+- Network isolation per application
2934 - Single binary, ~15MB RAM
3035
31-## Quick Start
36+## Documentation
3237
33-### Prerequisites
34-- Ubuntu/Debian VPS with root access
35-- Domain pointing to your VPS
36-- Cloudflare account (free tier works) - **Required for HTTPS**
38+Full documentation is available at **[gordon.bnema.dev](https://gordon.bnema.dev)**
3739
38-> **HTTPS Architecture:** Cloudflare terminates HTTPS and proxies to Gordon over HTTP internally. Gordon doesn't handle TLS certificates directly (no Let's Encrypt support yet).
40+- [Documentation](https://gordon.bnema.dev/docs) - Installation, configuration, CLI reference
41+- [Wiki](https://gordon.bnema.dev/wiki) - Tutorials, guides, and examples
3942
40-### Installation
43+## Quick Start
4144
4245 ```bash
4346 # Download and install
@@ -50,333 +53,14 @@ sudo mv gordon /usr/local/bin/
5053 gordon start
5154 ```
5255
53-Config is created at `~/.config/gordon/gordon.toml` on first start. Edit it to set your `registry_domain` and routes.
54-
55-**Important:** For a complete working setup including networking, firewall, and systemd service, follow the [detailed setup guide](#detailed-setup-guide-podman-rootless-mode) below.
56-
57-## Core Concepts
58-
59-### Local-First Development
60-Your dev machine likely has 8-16 cores and 16-32GB RAM. Your VPS has 1-2 cores and 1-4GB RAM. Why build containers on the weak machine? Gordon lets you build locally and deploy the finished product.
61-
62-### Push-to-Deploy Workflow
63-```bash
64-# Initial deployment
65-docker build -t myapp .
66-docker push registry.mydomain.com/myapp:latest
67-# Visit https://app.mydomain.com
68-
69-# Update deployment
70-docker build -t myapp .
71-docker push registry.mydomain.com/myapp:latest
72-# Zero-downtime update applied automatically
73-```
74-
75-### Automatic Volume Management
76-```dockerfile
77-# In your Dockerfile
78-FROM postgres:15
79-VOLUME ["/var/lib/postgresql/data"] # Gordon creates persistent storage
80-
81-FROM node:18
82-VOLUME ["/app/uploads", "/app/data"] # Multiple volumes supported
83-```
84-
85-### Proxy Port Selection
86-When an image exposes multiple ports, Gordon needs to know which one to proxy HTTP traffic to. By default, Gordon uses the first exposed port, but you can specify the exact port with a label:
87-
88-```dockerfile
89-# Gitea exposes both SSH (22) and HTTP (3000)
90-FROM gitea/gitea:latest
91-LABEL gordon.proxy.port=3000
92-EXPOSE 22
93-EXPOSE 3000
94-```
95-
96-Without the label, Gordon would proxy to port 22 (SSH), which would fail. The `gordon.proxy.port` label tells Gordon to route HTTP traffic to port 3000.
97-
98-**When to use this label:**
99-- Images that expose multiple ports (web + SSH, web + metrics, etc.)
100-- When the first exposed port isn't the HTTP service
101-- To be explicit about which port serves your web application
102-
103-### Environment Configuration
104-Gordon automatically creates and loads env files based on domain names:
105-```bash
106-# Auto-created on first run: ~/.gordon/env/app_mydomain_com.env
107-# Just add your variables:
108-DATABASE_URL=postgresql://localhost:5432/myapp
109-API_KEY=sk-1234567890
110-NODE_ENV=production
111-```
112-Supports secrets from `pass` and `SOPS`. See [examples/](examples/) for advanced usage.
113-
114-### Network Isolation
115-Each app runs in its own isolated network by default. Attach services for internal communication:
116-```toml
117-[attachments]
118-"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]
119-# Access services via hostname: my-postgres:5432, my-redis:6379
120-```
121-Gordon also supports network groups, see [examples/](examples/) for advanced usage.
122-
123-### Persistent Storage
124-- **Volumes**: Automatically created from Dockerfile VOLUME directives, persist across updates
125-
126-### Production ready logging
127-- **Logs**: All container output saved to `~/.gordon/logs/containers/`
128-- **Rotation**: Logs rotate at 100MB, keeping 3 backups for 28 days
129-
130-## Configuration
131-
132-Gordon uses a single TOML file for all configuration:
133-
134-```toml
135-# ~/.config/gordon/gordon.toml
136-[server]
137-registry_domain = "registry.mydomain.com" # Required
138-
139-# Secrets backend for sensitive data (tokens, passwords)
140-[secrets]
141-backend = "pass" # "pass", "sops", or "unsafe" (plain text in data_dir)
142-
143-# Registry authentication (choose password or token type)
144-[registry_auth]
145-enabled = true
146-type = "password" # "password" or "token"
147-# Password auth: bcrypt hash stored in secrets backend
148-username = "deploy"
149-password_hash = "gordon/registry/password_hash" # path in secrets backend
150-# Token auth: JWT-based authentication
151-# token_secret = "gordon/registry/token_secret" # path in secrets backend
152-
153-[routes]
154-"app.mydomain.com" = "myapp:latest" # Domain → Image mapping
155-"api.mydomain.com" = "myapi:v2.1.0" # Pin specific versions
156-
157-# Optional: Attach services to apps
158-[attachments]
159-"app.mydomain.com" = ["postgres:latest", "redis:latest"]
160-# Services accessible via internal DNS: postgres:5432, redis:6379
161-```
162-
163-See [examples/](examples/) for advanced configurations including network groups and more.
164-
165-### Authentication Types
166-
167-**Password Authentication** (simple):
168-```bash
169-# Generate a bcrypt hash for your password
170-gordon auth password hash
171-# Store the hash in your secrets backend, then reference it in config
172-```
173-
174-**Token Authentication** (recommended for CI/CD):
175-```bash
176-# Generate a never-expiring token for CI
177-gordon auth token generate --subject ci-bot --scopes push,pull --expiry 0
178-
179-# List all tokens
180-gordon auth token list
181-
182-# Revoke a compromised token
183-gordon auth token revoke <token-id>
184-```
185-
186-Tokens are stored in your configured secrets backend and are unique to each Gordon instance (different `token_secret` = incompatible tokens).
187-
188-### GitHub Actions Integration
189-
190-Gordon provides an official GitHub Action for automated deployments on tag push:
191-
192-```yaml
193-# .github/workflows/deploy.yml
194-name: Deploy to Gordon
195-
196-on:
197- push:
198- tags:
199- - 'v*'
200-
201-jobs:
202- deploy:
203- runs-on: ubuntu-latest
204- steps:
205- - uses: actions/checkout@v6
206-
207- - name: Deploy to Gordon
208- uses: bnema/gordon/.github/actions/deploy@main
209- with:
210- registry: ${{ secrets.GORDON_REGISTRY }}
211- username: ${{ secrets.GORDON_USERNAME }}
212- password: ${{ secrets.GORDON_TOKEN }}
213-```
214-
215-**Setup:**
216-1. Generate a CI token: `gordon auth token generate --subject github-actions --scopes push,pull --expiry 0`
217-2. Add secrets to your GitHub repository: `GORDON_REGISTRY`, `GORDON_USERNAME`, `GORDON_TOKEN`
218-3. Push a tag to deploy: `git tag v1.0.0 && git push origin v1.0.0`
219-
220-See [examples/github-workflow.yml](examples/github-workflow.yml) and [.github/actions/deploy/README.md](.github/actions/deploy/README.md) for advanced options.
221-
222-## Detailed Setup Guide (Podman Rootless Mode)
223-
224-> **Note:** This guide uses Podman in rootless mode for enhanced security. Docker users can follow similar steps. Gordon works with any OCI-compatible runtime.
225-
226-### 1. System Preparation
227-```bash
228-# Update system
229-sudo apt update && sudo apt upgrade -y
230-
231-# Install podman and firewalld
232-sudo apt install -y podman firewalld
233-
234-# Enable and start firewalld
235-sudo systemctl enable --now firewalld
236-```
237-
238-### 2. Firewall & Port Forwarding (rootless requires this)
239-
240-Rootless containers can't bind to privileged ports (< 1024). Use firewalld to redirect 80/443 to unprivileged ports:
241-
242-```bash
243-# Allow HTTP and HTTPS traffic
244-sudo firewall-cmd --permanent --add-service=http
245-sudo firewall-cmd --permanent --add-service=https
246-
247-# Port forward 80/443 to rootless container ports
248-sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
249-sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
250-
251-# Set default policy to drop incoming traffic
252-sudo firewall-cmd --permanent --zone=public --set-target=DROP
253-
254-# Reload to apply
255-sudo firewall-cmd --reload
256-
257-# Verify rules
258-sudo firewall-cmd --list-all
259-```
260-
261-> **Note:** If using Tailscale for management, add its interface to the trusted zone:
262-> ```bash
263-> sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
264-> sudo firewall-cmd --permanent --add-port=41641/udp # Tailscale WireGuard
265-> sudo firewall-cmd --reload
266-> ```
267-
268-### 3. Rootless Container Setup
269-```bash
270-# Enable user namespaces
271-echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
272-sudo sysctl -p
273-
274-# Setup subuid/subgid for your user
275-sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
276-
277-# Enable podman socket
278-systemctl --user enable --now podman.socket
279-
280-# Allow localhost registry (podman config)
281-mkdir -p ~/.config/containers
282-cat > ~/.config/containers/registries.conf <<EOF
283-[registries.insecure]
284-registries = ['localhost:5000']
285-EOF
286-```
287-
288-### 4. Install Gordon
289-```bash
290-# Download from releases
291-wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
292-tar -xzf gordon_linux_amd64.tar.gz
293-chmod +x gordon
294-sudo mv gordon /usr/local/bin/
295-```
296-
297-### 5. Configure Gordon
298-```bash
299-# Run once to generate config, then stop with Ctrl+C
300-gordon start
301-
302-# Edit the generated config
303-nano ~/.config/gordon/gordon.toml
304-```
305-
306-Set your `registry_domain` and add routes. Data is stored in `~/.gordon/`.
307-
308-### 6. Create Systemd Service
309-```bash
310-mkdir -p ~/.config/systemd/user
311-cat > ~/.config/systemd/user/gordon.service <<EOF
312-[Unit]
313-Description=Gordon Container Platform
314-After=podman.socket
315-
316-[Service]
317-Type=simple
318-Restart=always
319-RestartSec=5
320-ExecStart=/usr/local/bin/gordon start
321-
322-[Install]
323-WantedBy=default.target
324-EOF
325-
326-# Enable and start
327-systemctl --user daemon-reload
328-systemctl --user enable --now gordon
329-sudo loginctl enable-linger $USER
330-
331-# Verify
332-systemctl --user status gordon
333-journalctl --user -u gordon -f # Watch logs
334-```
335-
336-## Deployment Strategies
337-
338-### Simple Deployment
339-```bash
340-# Build locally
341-docker build -t myapp .
342-
343-# Test locally
344-docker run -p 8080:8080 myapp
345-
346-# Push to registry
347-docker tag myapp registry.mydomain.com/myapp:latest
348-docker push registry.mydomain.com/myapp:latest
349-
350-# Profit!
351-```
352-
353-### Versioned Deployment
354-```bash
355-# Deploy specific version
356-docker tag myapp:v1.2.0 registry.mydomain.com/myapp:v1.2.0
357-docker push registry.mydomain.com/myapp:v1.2.0
358-
359-# Update route in gordon.toml
360-# "app.mydomain.com" = "myapp:v1.2.0"
361-```
362-
363-### Advanced: Manifest Annotations
364-```bash
365-# Deploy with metadata
366-export VERSION=v1.2.0
367-docker manifest create myapp:latest
368-docker manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
369-docker manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
370-docker manifest push myapp:latest registry.mydomain.com/myapp:latest
371-```
56+Config is created at `~/.config/gordon/gordon.toml`. See the [Getting Started guide](https://gordon.bnema.dev/docs/getting-started) for complete setup instructions.
37257
37358 ## Community
37459
375-Gordon is open source and welcomes contributions:
37660 - [Report bugs](https://github.com/bnema/gordon/issues)
377-- [Suggest features](https://github.com/bnema/gordon/discussions)
61+- [Discussions](https://github.com/bnema/gordon/discussions)
37862 - [Submit PRs](https://github.com/bnema/gordon/pulls)
37963
38064 ## License
38165
382-GPL-3.0 - Use freely, contribute back.⏎̸
66+GPL-3.0 - Use freely, contribute back.
A docs/cli/auth.md +224 -0
@@ -0,0 +1,224 @@
1+# gordon auth
2+
3+Manage registry authentication tokens and passwords.
4+
5+## Synopsis
6+
7+```bash
8+gordon auth token generate [options]
9+gordon auth token list [options]
10+gordon auth token revoke <token-id> [options]
11+gordon auth password hash
12+```
13+
14+## Commands
15+
16+### gordon auth token generate
17+
18+Generate a new JWT authentication token for registry access.
19+
20+```bash
21+gordon auth token generate --subject <name> [options]
22+```
23+
24+**Options:**
25+
26+| Option | Default | Description |
27+|--------|---------|-------------|
28+| `--subject` | (required) | Username/subject for the token |
29+| `--scopes` | `push,pull` | Comma-separated scopes |
30+| `--expiry` | `720h` | Duration until expiry (0 = never) |
31+| `-c, --config` | Auto | Path to config file |
32+
33+**Examples:**
34+
35+```bash
36+# CI token that never expires
37+gordon auth token generate --subject github-actions --expiry 0
38+
39+# Read-only token
40+gordon auth token generate --subject reader --scopes pull --expiry 720h
41+
42+# Push-only token for build system
43+gordon auth token generate --subject builder --scopes push --expiry 0
44+
45+# Temporary token (24 hours)
46+gordon auth token generate --subject temp --expiry 24h
47+```
48+
49+**Output:**
50+
51+```
52+Token generated successfully!
53+Subject: github-actions
54+Scopes: push, pull
55+Expiry: never
56+
57+Token (use as password with docker login):
58+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
59+
60+Usage: docker login -u github-actions -p <token> <registry>
61+```
62+
63+### gordon auth token list
64+
65+List all stored authentication tokens.
66+
67+```bash
68+gordon auth token list [options]
69+```
70+
71+**Options:**
72+
73+| Option | Default | Description |
74+|--------|---------|-------------|
75+| `-c, --config` | Auto | Path to config file |
76+
77+**Example:**
78+
79+```bash
80+gordon auth token list
81+```
82+
83+**Output:**
84+
85+```
86+ID Subject Expires Revoked
87+------------------------------------------------------------------------------------------
88+a1b2c3d4-e5f6-7890-abcd-ef1234567890 github-actions never no
89+b2c3d4e5-f6a7-8901-bcde-f12345678901 deploy-bot 2024-02-15 10:30 no
90+c3d4e5f6-a7b8-9012-cdef-123456789012 old-token 2024-01-01 00:00 yes
91+```
92+
93+### gordon auth token revoke
94+
95+Revoke a token by its ID.
96+
97+```bash
98+gordon auth token revoke <token-id> [options]
99+```
100+
101+**Options:**
102+
103+| Option | Default | Description |
104+|--------|---------|-------------|
105+| `-c, --config` | Auto | Path to config file |
106+
107+**Example:**
108+
109+```bash
110+gordon auth token revoke a1b2c3d4-e5f6-7890-abcd-ef1234567890
111+```
112+
113+**Output:**
114+
115+```
116+Token a1b2c3d4-e5f6-7890-abcd-ef1234567890 has been revoked.
117+```
118+
119+### gordon auth password hash
120+
121+Generate a bcrypt hash for password authentication.
122+
123+```bash
124+gordon auth password hash
125+```
126+
127+Interactively prompts for a password and outputs the bcrypt hash.
128+
129+**Example:**
130+
131+```bash
132+gordon auth password hash
133+```
134+
135+**Output:**
136+
137+```
138+Enter password: ********
139+
140+Bcrypt hash (store in your secrets backend):
141+$2a$10$N9qo8uLOickgx2ZMRZoMye...
142+
143+Then reference the path in your config:
144+ [registry_auth]
145+ type = "password"
146+ password_hash = "gordon/registry/password_hash"
147+```
148+
149+## Token Scopes
150+
151+| Scope | Permission |
152+|-------|------------|
153+| `push` | Push images to registry |
154+| `pull` | Pull images from registry |
155+| `push,pull` | Both push and pull (default) |
156+
157+## Token Expiry Formats
158+
159+| Format | Duration |
160+|--------|----------|
161+| `0` | Never expires |
162+| `24h` | 24 hours |
163+| `720h` | 30 days |
164+| `8760h` | 1 year |
165+
166+## Workflow Examples
167+
168+### CI/CD Setup
169+
170+```bash
171+# Generate token for CI
172+gordon auth token generate --subject ci-bot --scopes push,pull --expiry 0
173+
174+# Save token as CI secret
175+# GitHub: Settings → Secrets → New repository secret
176+# GitLab: Settings → CI/CD → Variables
177+```
178+
179+### Docker Login
180+
181+```bash
182+# Using generated token
183+docker login -u ci-bot -p <token> registry.mydomain.com
184+
185+# In CI/CD
186+echo "$GORDON_TOKEN" | docker login -u ci-bot --password-stdin registry.mydomain.com
187+```
188+
189+### Token Rotation
190+
191+```bash
192+# Generate new token
193+gordon auth token generate --subject ci-bot --expiry 0
194+
195+# Update CI secrets with new token
196+
197+# Revoke old token
198+gordon auth token revoke <old-token-id>
199+```
200+
201+### Password Setup
202+
203+```bash
204+# Generate hash
205+gordon auth password hash
206+# Enter: mypassword
207+
208+# Store in pass
209+pass insert gordon/registry/password_hash
210+# Paste: $2a$10$...
211+
212+# Configure gordon.toml
213+[registry_auth]
214+enabled = true
215+type = "password"
216+username = "deploy"
217+password_hash = "gordon/registry/password_hash"
218+```
219+
220+## Related
221+
222+- [Registry Authentication](../config/registry-auth.md)
223+- [Secrets Configuration](../config/secrets.md)
224+- [CLI Overview](./index.md)
A docs/cli/index.md +151 -0
@@ -0,0 +1,151 @@
1+# CLI Reference
2+
3+Gordon provides a command-line interface for server management and authentication.
4+
5+## Commands
6+
7+| Command | Description |
8+|---------|-------------|
9+| `gordon start` | Start the Gordon server |
10+| `gordon reload` | Reload configuration and sync containers |
11+| `gordon logs` | Display Gordon process logs |
12+| `gordon version` | Print version information |
13+| `gordon auth` | Manage registry authentication |
14+
15+## Quick Reference
16+
17+```bash
18+# Start Gordon
19+gordon start
20+gordon start --config /path/to/config.toml
21+
22+# Reload configuration
23+gordon reload
24+
25+# View logs
26+gordon logs
27+gordon logs -f
28+gordon logs -n 100
29+
30+# Check version
31+gordon version
32+
33+# Authentication
34+gordon auth token generate --subject ci-bot --expiry 0
35+gordon auth token list
36+gordon auth token revoke <token-id>
37+gordon auth password hash
38+```
39+
40+## Global Options
41+
42+| Option | Description |
43+|--------|-------------|
44+| `-c, --config` | Path to configuration file |
45+
46+## Command Details
47+
48+### gordon start
49+
50+Starts the Gordon server with registry and proxy components.
51+
52+```bash
53+gordon start [options]
54+```
55+
56+**Options:**
57+- `-c, --config string` - Path to config file
58+
59+**Example:**
60+```bash
61+gordon start
62+gordon start --config ~/.config/gordon/gordon.toml
63+gordon start -c /etc/gordon/gordon.toml
64+```
65+
66+See [gordon start](./start.md) for details.
67+
68+### gordon reload
69+
70+Sends a reload signal to the running Gordon process. Reloads configuration and syncs containers to match.
71+
72+```bash
73+gordon reload
74+```
75+
76+This sends `SIGUSR1` to the Gordon process, triggering:
77+- Configuration file reload
78+- Route synchronization
79+- Attachment deployment
80+
81+### gordon logs
82+
83+Displays logs from the Gordon process.
84+
85+```bash
86+gordon logs [options]
87+```
88+
89+**Options:**
90+- `-c, --config string` - Path to config file
91+- `-f, --follow` - Follow log output (like `tail -f`)
92+- `-n, --lines int` - Number of lines to show (default: 50)
93+
94+**Examples:**
95+```bash
96+gordon logs # Last 50 lines
97+gordon logs -f # Follow logs
98+gordon logs -n 100 # Last 100 lines
99+gordon logs -f -n 200 # Follow, starting from last 200 lines
100+```
101+
102+### gordon version
103+
104+Prints version information.
105+
106+```bash
107+gordon version
108+```
109+
110+**Output:**
111+```
112+Gordon v2.0.0
113+Commit: abc1234
114+Build Date: 2024-01-15
115+```
116+
117+### gordon auth
118+
119+Manage registry authentication. See [gordon auth](./auth.md) for details.
120+
121+```bash
122+gordon auth token generate # Generate JWT token
123+gordon auth token list # List all tokens
124+gordon auth token revoke # Revoke a token
125+gordon auth password hash # Generate bcrypt hash
126+```
127+
128+## Exit Codes
129+
130+| Code | Meaning |
131+|------|---------|
132+| 0 | Success |
133+| 1 | General error |
134+| 2 | Configuration error |
135+
136+## Environment Variables
137+
138+Gordon reads configuration from environment variables:
139+
140+```bash
141+GORDON_SERVER_PORT=8080 gordon start
142+GORDON_LOGGING_LEVEL=debug gordon start
143+```
144+
145+Pattern: `GORDON_SECTION_KEY` (uppercase, underscores)
146+
147+## Related
148+
149+- [gordon start](./start.md)
150+- [gordon auth](./auth.md)
151+- [Configuration Reference](../config/index.md)
A docs/cli/start.md +161 -0
@@ -0,0 +1,161 @@
1+# gordon start
2+
3+Start the Gordon server with registry and proxy components.
4+
5+## Synopsis
6+
7+```bash
8+gordon start [options]
9+```
10+
11+## Options
12+
13+| Option | Short | Default | Description |
14+|--------|-------|---------|-------------|
15+| `--config` | `-c` | Auto-detected | Path to configuration file |
16+
17+## Description
18+
19+Starts the Gordon server, which includes:
20+
21+- **Container Registry** - Receives image pushes on the registry port
22+- **HTTP Proxy** - Routes traffic to containers on the proxy port
23+- **Event Bus** - Coordinates deployments and updates
24+- **Config Watcher** - Monitors configuration file for changes
25+
26+## Configuration File Detection
27+
28+Gordon looks for configuration in this order:
29+
30+1. Path specified with `--config`
31+2. `/etc/gordon/gordon.toml`
32+3. `~/.config/gordon/gordon.toml`
33+4. `./gordon.toml`
34+
35+## First Run
36+
37+On first run without a config file, Gordon creates a default configuration at `~/.config/gordon/gordon.toml`.
38+
39+```bash
40+# First run - creates default config
41+gordon start
42+# Edit the config, then restart
43+```
44+
45+## Examples
46+
47+### Basic Start
48+
49+```bash
50+gordon start
51+```
52+
53+### With Custom Config
54+
55+```bash
56+gordon start --config /path/to/gordon.toml
57+gordon start -c ./my-config.toml
58+```
59+
60+### With Environment Override
61+
62+```bash
63+GORDON_SERVER_PORT=8080 gordon start
64+GORDON_LOGGING_LEVEL=debug gordon start
65+```
66+
67+## Signals
68+
69+Gordon responds to these signals:
70+
71+| Signal | Action |
72+|--------|--------|
73+| `SIGTERM` | Graceful shutdown |
74+| `SIGINT` | Graceful shutdown (Ctrl+C) |
75+| `SIGUSR1` | Reload configuration |
76+
77+## Process Management
78+
79+### Running in Foreground
80+
81+```bash
82+gordon start
83+# Press Ctrl+C to stop
84+```
85+
86+### Running with systemd
87+
88+```bash
89+# Create user service
90+mkdir -p ~/.config/systemd/user
91+cat > ~/.config/systemd/user/gordon.service <<EOF
92+[Unit]
93+Description=Gordon Container Platform
94+
95+[Service]
96+Type=simple
97+Restart=always
98+ExecStart=/usr/local/bin/gordon start
99+
100+[Install]
101+WantedBy=default.target
102+EOF
103+
104+# Enable and start
105+systemctl --user daemon-reload
106+systemctl --user enable --now gordon
107+sudo loginctl enable-linger $USER
108+```
109+
110+### Check Status
111+
112+```bash
113+# systemd
114+systemctl --user status gordon
115+
116+# Process
117+pgrep -f "gordon start"
118+```
119+
120+## Startup Sequence
121+
122+1. Load configuration
123+2. Initialize logger
124+3. Create PID file
125+4. Connect to Docker/Podman runtime
126+5. Create storage directories
127+6. Initialize services (registry, proxy, auth)
128+7. Register event handlers
129+8. Start config file watcher
130+9. Sync existing containers
131+10. Start HTTP servers
132+
133+## Shutdown Sequence
134+
135+1. Receive shutdown signal
136+2. Stop accepting new requests
137+3. Complete in-flight requests
138+4. Stop managed containers (if configured)
139+5. Remove PID file
140+6. Exit
141+
142+## Logs
143+
144+View startup and runtime logs:
145+
146+```bash
147+# Using gordon logs
148+gordon logs -f
149+
150+# Direct file access
151+tail -f ~/.gordon/logs/gordon.log
152+
153+# With systemd
154+journalctl --user -u gordon -f
155+```
156+
157+## Related
158+
159+- [CLI Overview](./index.md)
160+- [Configuration](../config/index.md)
161+- [Installation](../installation.md)
A docs/concepts.md +243 -0
@@ -0,0 +1,243 @@
1+# Core Concepts
2+
3+Understanding how Gordon works and why it's designed this way.
4+
5+## Local-First Development
6+
7+Your development machine likely has 8-16 cores and 16-32GB RAM. Your VPS has 1-2 cores and 1-4GB RAM. Why build containers on the weak machine?
8+
9+Gordon flips the typical deployment model:
10+
11+1. **Build locally** where you have computing power
12+2. **Push the finished image** to your VPS
13+3. **Gordon deploys automatically**
14+
15+This means faster builds, less VPS resource usage, and a simpler deployment workflow.
16+
17+## Push-to-Deploy
18+
19+Gordon combines a Docker registry with automatic deployment:
20+
21+```
22+┌──────────────┐ push ┌──────────────┐
23+│ docker build │ ──────────────>│ Gordon │
24+│ docker push │ │ Registry │
25+└──────────────┘ └──────┬───────┘
26+ │
27+ │ event: image.pushed
28+ v
29+ ┌──────────────┐
30+ │ Deploy │
31+ │ Container │
32+ └──────────────┘
33+```
34+
35+When you push an image, Gordon:
36+
37+1. Stores the image in its registry
38+2. Fires an `image.pushed` event
39+3. Looks up the route for that image
40+4. Deploys a new container
41+5. Updates the proxy routing
42+6. Stops the old container
43+
44+## Zero-Downtime Updates
45+
46+Gordon ensures your app stays available during updates:
47+
48+1. **New container starts** while old container is still running
49+2. **Health check** waits for new container to be ready
50+3. **Traffic switches** to the new container
51+4. **Old container stops** after traffic has moved
52+
53+```
54+Time ─────────────────────────────────────────────>
55+
56+Old Container: [═══════════════════]
57+ ↓ stop
58+New Container: [═════════════════════════>
59+ ↑ start ↑ traffic routed
60+```
61+
62+## Routes
63+
64+Routes map domains to container images:
65+
66+```toml
67+[routes]
68+"app.mydomain.com" = "myapp:latest"
69+"api.mydomain.com" = "myapi:v2.1.0"
70+```
71+
72+When a request comes in for `app.mydomain.com`, Gordon:
73+
74+1. Looks up the route configuration
75+2. Finds the running container for `myapp:latest`
76+3. Proxies the request to that container
77+
78+### HTTP vs HTTPS Routes
79+
80+By default, routes expect HTTPS (terminated by Cloudflare). For HTTP-only routes:
81+
82+```toml
83+[routes]
84+"http://internal.local" = "internal-app:latest"
85+```
86+
87+## Network Isolation
88+
89+Each app runs in its own isolated Docker network:
90+
91+```
92+┌─────────────────────────────────────────────────────┐
93+│ gordon-app-mydomain-com │
94+│ │
95+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
96+│ │ App │───>│ Postgres │ │ Redis │ │
97+│ │ :3000 │ │ :5432 │ │ :6379 │ │
98+│ └──────────┘ └──────────┘ └──────────┘ │
99+│ │
100+└─────────────────────────────────────────────────────┘
101+```
102+
103+Benefits:
104+
105+- Containers can't access each other's services
106+- Services are only accessible by name within their network
107+- No port conflicts between apps
108+
109+## Attachments
110+
111+Attachments are service dependencies for your apps:
112+
113+```toml
114+[attachments]
115+"app.mydomain.com" = ["postgres:latest", "redis:latest"]
116+```
117+
118+Gordon deploys attachments to the same network as your app. Services are accessible by their image name:
119+
120+```javascript
121+// In your app
122+const db = await connect("postgresql://postgres:5432/mydb");
123+const cache = await connect("redis://redis:6379");
124+```
125+
126+## Network Groups
127+
128+Network groups allow multiple apps to share services:
129+
130+```toml
131+[network_groups]
132+"backend" = ["app.mydomain.com", "api.mydomain.com"]
133+
134+[attachments]
135+"backend" = ["shared-postgres:latest", "shared-redis:latest"]
136+```
137+
138+Both `app.mydomain.com` and `api.mydomain.com` can access the shared services.
139+
140+## Volumes
141+
142+Gordon automatically creates persistent storage from Dockerfile `VOLUME` directives:
143+
144+```dockerfile
145+FROM postgres:15
146+VOLUME ["/var/lib/postgresql/data"]
147+```
148+
149+Volume behavior:
150+
151+- **auto_create**: Volumes are created automatically (default: true)
152+- **prefix**: Volume names are prefixed with `gordon-` (configurable)
153+- **preserve**: Volumes persist across container updates (default: true)
154+
155+## Environment Variables
156+
157+Gordon loads environment variables from files based on the domain:
158+
159+```
160+~/.gordon/env/
161+├── app_mydomain_com.env
162+├── api_mydomain_com.env
163+└── admin_mydomain_com.env
164+```
165+
166+Domain dots become underscores: `app.mydomain.com` → `app_mydomain_com.env`
167+
168+Variables are merged in order:
169+
170+1. Dockerfile `ENV` directives (lowest priority)
171+2. `.env` file values (highest priority)
172+
173+### Secret Providers
174+
175+Environment files support secret provider syntax:
176+
177+```bash
178+# From Unix password manager (pass)
179+DATABASE_PASSWORD=${pass:myapp/db-password}
180+
181+# From SOPS encrypted files
182+API_SECRET=${sops:secrets.yaml:api.secret}
183+```
184+
185+## Configuration Hot-Reload
186+
187+Gordon watches its config file and reloads automatically:
188+
189+1. Edit `~/.config/gordon/gordon.toml`
190+2. Save the file
191+3. Gordon reloads routes, attachments, and network groups
192+4. Containers sync to match new configuration
193+
194+You can also trigger a manual reload:
195+
196+```bash
197+gordon reload
198+```
199+
200+This sends `SIGUSR1` to the running Gordon process.
201+
202+## Event System
203+
204+Gordon uses an internal event system for coordination:
205+
206+| Event | Trigger | Action |
207+|-------|---------|--------|
208+| `image.pushed` | Image pushed to registry | Deploy container |
209+| `config.reload` | Config file changed | Sync containers |
210+| `manual.reload` | `gordon reload` command | Sync containers |
211+| `container.deployed` | Container started | Update proxy cache |
212+
213+## Container Labels
214+
215+Gordon uses labels to track managed containers:
216+
217+| Label | Purpose |
218+|-------|---------|
219+| `gordon.managed=true` | Identifies Gordon-managed containers |
220+| `gordon.domain` | Domain this container serves |
221+| `gordon.image` | Image name and tag |
222+| `gordon.route` | Route this container handles |
223+| `gordon.attachment=true` | Container is an attachment service |
224+| `gordon.attached-to` | Which route this attachment serves |
225+
226+## Proxy Port Selection
227+
228+When a container exposes multiple ports, Gordon needs to know which one serves HTTP:
229+
230+```dockerfile
231+FROM gitea/gitea:latest
232+LABEL gordon.proxy.port=3000 # Route HTTP to port 3000
233+EXPOSE 22 # SSH
234+EXPOSE 3000 # HTTP
235+```
236+
237+Without the label, Gordon uses the first exposed port.
238+
239+## Related
240+
241+- [Configuration Reference](./config/index.md)
242+- [Docker Labels Reference](./reference/docker-labels.md)
243+- [Environment Variables](./config/env.md)
A docs/config/attachments.md +211 -0
@@ -0,0 +1,211 @@
1+# Attachments Configuration
2+
3+Attach service dependencies (databases, caches, queues) to your applications.
4+
5+## Configuration
6+
7+```toml
8+[attachments]
9+"app.mydomain.com" = ["postgres:latest", "redis:latest"]
10+"api.mydomain.com" = ["postgres:latest"]
11+```
12+
13+## Syntax
14+
15+```toml
16+[attachments]
17+"<domain-or-group>" = ["<image>:<tag>", "<image>:<tag>"]
18+```
19+
20+| Component | Description |
21+|-----------|-------------|
22+| `domain-or-group` | Route domain or network group name |
23+| `image:tag` | Service images to deploy alongside the app |
24+
25+## How Attachments Work
26+
27+When you define attachments:
28+
29+1. Gordon deploys attachment containers to the same network as your app
30+2. Services are accessible by their image name (before the colon)
31+3. Attachments start before your main application
32+4. Attachments persist across app updates
33+
34+```
35+┌─────────────────────────────────────────────────────┐
36+│ Network: gordon-app-mydomain-com │
37+│ │
38+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
39+│ │ App │───>│ postgres │ │ redis │ │
40+│ │ :3000 │ │ :5432 │ │ :6379 │ │
41+│ └──────────┘ └──────────┘ └──────────┘ │
42+│ │
43+└─────────────────────────────────────────────────────┘
44+```
45+
46+## Service Discovery
47+
48+Attachments are accessible by their image name within the network:
49+
50+```toml
51+[attachments]
52+"app.mydomain.com" = ["postgres:latest", "redis:latest"]
53+```
54+
55+In your application:
56+```javascript
57+// Connect using simple hostnames
58+const db = await pg.connect("postgresql://postgres:5432/mydb");
59+const cache = await redis.connect("redis://redis:6379");
60+```
61+
62+## Persistent Storage
63+
64+Use Dockerfile VOLUME directives for persistent data:
65+
66+```dockerfile
67+# postgres.Dockerfile
68+FROM postgres:15
69+VOLUME ["/var/lib/postgresql/data"]
70+ENV POSTGRES_DB=myapp
71+ENV POSTGRES_USER=app
72+ENV POSTGRES_PASSWORD=secret
73+```
74+
75+Build and push to Gordon:
76+```bash
77+docker build -f postgres.Dockerfile -t my-postgres:latest .
78+docker push registry.mydomain.com/my-postgres:latest
79+```
80+
81+Use in attachments:
82+```toml
83+[attachments]
84+"app.mydomain.com" = ["my-postgres:latest"]
85+```
86+
87+## Shared Attachments with Network Groups
88+
89+Share services between multiple apps using network groups:
90+
91+```toml
92+[network_groups]
93+"backend" = ["app.mydomain.com", "api.mydomain.com"]
94+
95+[attachments]
96+"backend" = ["shared-postgres:latest", "shared-redis:latest"]
97+```
98+
99+Both `app.mydomain.com` and `api.mydomain.com` can access the shared services.
100+
101+## Per-App vs Shared Attachments
102+
103+### Per-App Attachments
104+
105+Each app gets its own isolated service instances:
106+
107+```toml
108+[attachments]
109+"app.mydomain.com" = ["postgres:latest"] # App's own postgres
110+"api.mydomain.com" = ["postgres:latest"] # API's own postgres
111+```
112+
113+### Shared Attachments
114+
115+Multiple apps share the same service instances:
116+
117+```toml
118+[network_groups]
119+"backend" = ["app.mydomain.com", "api.mydomain.com"]
120+
121+[attachments]
122+"backend" = ["postgres:latest", "redis:latest"] # Shared by both
123+```
124+
125+### Mixed Approach
126+
127+```toml
128+[network_groups]
129+"backend" = ["app.mydomain.com", "api.mydomain.com"]
130+
131+[attachments]
132+"backend" = ["redis:latest"] # Shared cache
133+"app.mydomain.com" = ["postgres:latest"] # App's own database
134+"api.mydomain.com" = ["postgres:latest"] # API's own database
135+```
136+
137+## Examples
138+
139+### Web App with Database
140+
141+```toml
142+[routes]
143+"app.mydomain.com" = "myapp:latest"
144+
145+[attachments]
146+"app.mydomain.com" = ["postgres:15", "redis:7-alpine"]
147+```
148+
149+### Microservices with Shared Queue
150+
151+```toml
152+[routes]
153+"orders.mydomain.com" = "orders-service:latest"
154+"inventory.mydomain.com" = "inventory-service:latest"
155+"notifications.mydomain.com" = "notifications-service:latest"
156+
157+[network_groups]
158+"services" = ["orders.mydomain.com", "inventory.mydomain.com", "notifications.mydomain.com"]
159+
160+[attachments]
161+"services" = ["rabbitmq:3-management"]
162+"orders.mydomain.com" = ["orders-db:latest"]
163+"inventory.mydomain.com" = ["inventory-db:latest"]
164+```
165+
166+### Custom Service Images
167+
168+Build custom service images with your configuration:
169+
170+```dockerfile
171+# my-postgres.Dockerfile
172+FROM postgres:15
173+VOLUME ["/var/lib/postgresql/data"]
174+ENV POSTGRES_DB=production
175+ENV POSTGRES_USER=appuser
176+# Password should come from env file
177+```
178+
179+```dockerfile
180+# my-redis.Dockerfile
181+FROM redis:7-alpine
182+VOLUME ["/data"]
183+CMD ["redis-server", "--appendonly", "yes"]
184+```
185+
186+```toml
187+[attachments]
188+"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]
189+```
190+
191+## Container Naming
192+
193+Attachment containers are named:
194+- `gordon-<domain>-<image-name>`
195+- Example: `gordon-app-mydomain-com-postgres`
196+
197+## Labels
198+
199+Gordon adds labels to attachment containers:
200+
201+| Label | Value |
202+|-------|-------|
203+| `gordon.managed` | `true` |
204+| `gordon.attachment` | `true` |
205+| `gordon.attached-to` | Domain or group name |
206+
207+## Related
208+
209+- [Network Isolation](./network-isolation.md)
210+- [Network Groups](./network-groups.md)
211+- [Routes](./routes.md)
A docs/config/auto-route.md +167 -0
@@ -0,0 +1,167 @@
1+# Auto Route Configuration
2+
3+Automatically create routes from image names that contain domains.
4+
5+## Configuration
6+
7+```toml
8+[auto_route]
9+enabled = true
10+```
11+
12+## Options
13+
14+| Option | Type | Default | Description |
15+|--------|------|---------|-------------|
16+| `enabled` | bool | `false` | Enable automatic route creation |
17+
18+## How It Works
19+
20+When auto-route is enabled, Gordon automatically creates routes for images with domain-like names:
21+
22+```bash
23+# Push an image with a domain name
24+docker push registry.mydomain.com/app.example.com:latest
25+```
26+
27+Gordon automatically:
28+1. Detects `app.example.com` looks like a domain
29+2. Creates a route: `app.example.com` → `app.example.com:latest`
30+3. Deploys the container
31+4. Routes traffic to it
32+
33+## Domain Detection
34+
35+Gordon recognizes image names as domains when they:
36+
37+- Contain at least one dot (`.`)
38+- Don't start or end with a dot
39+- Look like valid hostnames
40+
41+| Image Name | Detected as Domain? |
42+|------------|-------------------|
43+| `app.example.com` | Yes |
44+| `myapp.dev` | Yes |
45+| `staging.api.io` | Yes |
46+| `myapp` | No (no dots) |
47+| `.invalid` | No (starts with dot) |
48+| `myapp.` | No (ends with dot) |
49+
50+## Use Cases
51+
52+### Simplified Deployment
53+
54+Without auto-route:
55+```toml
56+# Must define each route in config
57+[routes]
58+"app.example.com" = "app.example.com:latest"
59+```
60+
61+With auto-route:
62+```toml
63+[auto_route]
64+enabled = true
65+# No routes needed - just push!
66+```
67+
68+```bash
69+docker push registry.mydomain.com/app.example.com:latest
70+# Route automatically created
71+```
72+
73+### Development Workflow
74+
75+```bash
76+# Create a new app instantly
77+docker build -t myapp .
78+docker tag myapp registry.mydomain.com/newapp.mydomain.com:latest
79+docker push registry.mydomain.com/newapp.mydomain.com:latest
80+# newapp.mydomain.com is now live!
81+```
82+
83+### Multi-Tenant SaaS
84+
85+```bash
86+# Quickly provision customer subdomains
87+docker push registry.mydomain.com/acme.saas.com:latest
88+docker push registry.mydomain.com/beta.saas.com:latest
89+docker push registry.mydomain.com/gamma.saas.com:latest
90+```
91+
92+## Combining with Manual Routes
93+
94+Auto-route works alongside manual routes:
95+
96+```toml
97+[auto_route]
98+enabled = true
99+
100+[routes]
101+# Pinned versions override auto-route
102+"api.mydomain.com" = "myapi:v2.1.0"
103+```
104+
105+- `api.mydomain.com` uses the manually configured `myapi:v2.1.0`
106+- Other domain-named images create automatic routes
107+
108+## Priority Order
109+
110+1. **Manual routes** in `[routes]` take precedence
111+2. **Auto-routes** are created for domain-named images without manual routes
112+
113+## Examples
114+
115+### Enable Auto-Route
116+
117+```toml
118+[auto_route]
119+enabled = true
120+```
121+
122+### Development with Auto-Route
123+
124+```toml
125+[server]
126+registry_domain = "registry.local"
127+
128+[auto_route]
129+enabled = true
130+
131+# No routes defined - all come from image names
132+```
133+
134+Usage:
135+```bash
136+docker push registry.local/myapp.local:latest
137+docker push registry.local/api.local:latest
138+# Both routes created automatically
139+```
140+
141+### Production with Auto-Route
142+
143+```toml
144+[auto_route]
145+enabled = true
146+
147+[routes]
148+# Critical services with pinned versions
149+"api.company.com" = "company-api:v2.1.0"
150+"app.company.com" = "company-app:v1.5.0"
151+
152+# Other services can use auto-route
153+```
154+
155+## Limitations
156+
157+- Auto-routes always use the pushed image tag
158+- No support for HTTP-only routes (all HTTPS)
159+- No automatic attachment configuration
160+- Cannot pin versions (always uses pushed tag)
161+
162+For more control, define routes manually in `[routes]`.
163+
164+## Related
165+
166+- [Routes](./routes.md)
167+- [Configuration Overview](./index.md)
A docs/config/env.md +192 -0
@@ -0,0 +1,192 @@
1+# Environment Variables
2+
3+Configure per-application environment variables using domain-based files.
4+
5+## Configuration
6+
7+```toml
8+[env]
9+dir = "~/.gordon/env" # Default location
10+```
11+
12+## Options
13+
14+| Option | Type | Default | Description |
15+|--------|------|---------|-------------|
16+| `dir` | string | `~/.gordon/env` | Directory containing .env files |
17+
18+## How It Works
19+
20+Gordon loads environment variables from files named after the domain:
21+
22+```
23+~/.gordon/env/
24+├── app_mydomain_com.env # For app.mydomain.com
25+├── api_mydomain_com.env # For api.mydomain.com
26+└── admin_mydomain_com.env # For admin.mydomain.com
27+```
28+
29+**Naming convention:** Replace dots with underscores, add `.env` extension.
30+
31+| Domain | File Name |
32+|--------|-----------|
33+| `app.mydomain.com` | `app_mydomain_com.env` |
34+| `api.company.io` | `api_company_io.env` |
35+| `staging.app.dev` | `staging_app_dev.env` |
36+
37+## File Format
38+
39+Standard `.env` file format:
40+
41+```bash
42+# app_mydomain_com.env
43+NODE_ENV=production
44+PORT=3000
45+DATABASE_URL=postgresql://db:5432/myapp
46+API_KEY=sk-1234567890
47+DEBUG=false
48+```
49+
50+## Variable Merging
51+
52+Variables are merged from multiple sources (lowest to highest priority):
53+
54+1. **Dockerfile ENV** - Base defaults from image
55+2. **.env file** - Overrides Dockerfile values
56+
57+```dockerfile
58+# Dockerfile
59+ENV NODE_ENV=development
60+ENV PORT=3000
61+```
62+
63+```bash
64+# app_mydomain_com.env
65+NODE_ENV=production # Overrides Dockerfile
66+# PORT not set, uses 3000 from Dockerfile
67+```
68+
69+Result: `NODE_ENV=production`, `PORT=3000`
70+
71+## Secret Provider Syntax
72+
73+Reference secrets from configured backends in env files:
74+
75+### Pass (Unix Password Manager)
76+
77+```bash
78+DATABASE_PASSWORD=${pass:myapp/database/password}
79+API_SECRET=${pass:company/api-secret}
80+JWT_KEY=${pass:production/jwt-signing-key}
81+```
82+
83+### SOPS (Encrypted Files)
84+
85+```bash
86+DATABASE_PASSWORD=${sops:secrets.yaml:database.password}
87+API_SECRET=${sops:production.yaml:api.secret}
88+STRIPE_KEY=${sops:secrets.yaml:stripe.api_key}
89+```
90+
91+### Syntax Reference
92+
93+| Provider | Syntax | Example |
94+|----------|--------|---------|
95+| pass | `${pass:<path>}` | `${pass:myapp/db-password}` |
96+| sops | `${sops:<file>:<key.path>}` | `${sops:secrets.yaml:db.password}` |
97+
98+## Examples
99+
100+### Basic Application
101+
102+```bash
103+# app_mydomain_com.env
104+NODE_ENV=production
105+PORT=3000
106+LOG_LEVEL=info
107+```
108+
109+### Database Connection
110+
111+```bash
112+# ~/.gordon/env/api_mydomain_com.env
113+DATABASE_HOST=postgres
114+DATABASE_PORT=5432
115+DATABASE_NAME=myapi
116+DATABASE_USER=apiuser
117+DATABASE_PASSWORD=${pass:myapi/db-password}
118+DATABASE_URL=postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
119+```
120+
121+### Full Production Setup
122+
123+```bash
124+# ~/.gordon/env/app_company_com.env
125+# Application settings
126+NODE_ENV=production
127+PORT=3000
128+LOG_LEVEL=warn
129+
130+# Database
131+DATABASE_URL=postgresql://postgres:5432/production
132+DATABASE_PASSWORD=${pass:company/db-password}
133+
134+# Redis
135+REDIS_URL=redis://redis:6379
136+
137+# External APIs
138+STRIPE_SECRET_KEY=${pass:company/stripe-secret}
139+SENDGRID_API_KEY=${pass:company/sendgrid-key}
140+
141+# Auth
142+JWT_SECRET=${pass:company/jwt-secret}
143+SESSION_SECRET=${pass:company/session-secret}
144+
145+# Feature flags
146+ENABLE_ANALYTICS=true
147+MAINTENANCE_MODE=false
148+```
149+
150+### Development Environment
151+
152+```bash
153+# ~/.gordon/env/app_local.env
154+NODE_ENV=development
155+PORT=3000
156+LOG_LEVEL=debug
157+DATABASE_URL=postgresql://postgres:5432/dev
158+DEBUG=*
159+```
160+
161+## File Permissions
162+
163+Gordon creates env files with secure permissions:
164+- Directory: `0700` (owner only)
165+- Files: `0600` (owner only)
166+
167+Create files with proper permissions:
168+
169+```bash
170+mkdir -p ~/.gordon/env
171+chmod 700 ~/.gordon/env
172+touch ~/.gordon/env/app_mydomain_com.env
173+chmod 600 ~/.gordon/env/app_mydomain_com.env
174+```
175+
176+## Auto-Creation
177+
178+Gordon creates empty env files automatically when deploying a new route. You can then edit the file to add your variables.
179+
180+## Viewing Effective Environment
181+
182+To see what environment a container receives:
183+
184+```bash
185+docker inspect gordon-app-mydomain-com | jq '.[0].Config.Env'
186+```
187+
188+## Related
189+
190+- [Secrets Configuration](./secrets.md)
191+- [Routes](./routes.md)
192+- [Attachments](./attachments.md)
A docs/config/index.md +176 -0
@@ -0,0 +1,176 @@
1+# Configuration Overview
2+
3+Gordon uses a single TOML configuration file located at `~/.config/gordon/gordon.toml`.
4+
5+## Configuration File Location
6+
7+| Location | Purpose |
8+|----------|---------|
9+| `~/.config/gordon/gordon.toml` | Default configuration file |
10+| Custom path via `--config` flag | `gordon start --config /path/to/config.toml` |
11+
12+## Minimal Configuration
13+
14+```toml
15+[server]
16+port = 8080
17+registry_port = 5000
18+registry_domain = "registry.mydomain.com"
19+
20+[routes]
21+"app.mydomain.com" = "myapp:latest"
22+```
23+
24+## Full Configuration Reference
25+
26+> **Note:** This example shows production-style paths. Default paths use `~/.gordon/` for user installations.
27+
28+```toml
29+# Server settings
30+[server]
31+port = 8080 # HTTP proxy port (default: 80)
32+registry_port = 5000 # Registry port (default: 5000)
33+registry_domain = "registry.mydomain.com" # Required: registry domain
34+# data_dir = "~/.gordon" # Default for user installations
35+
36+# Secrets backend
37+[secrets]
38+backend = "pass" # "pass", "sops", or "unsafe"
39+
40+# Registry authentication
41+[registry_auth]
42+enabled = true
43+type = "token" # "password" or "token"
44+# Password auth:
45+# username = "deploy"
46+# password_hash = "gordon/registry/password_hash"
47+# Token auth:
48+token_secret = "gordon/registry/token_secret"
49+token_expiry = "720h" # Duration or 0 for never
50+
51+# Logging
52+[logging]
53+level = "info" # trace, debug, info, warn, error
54+format = "console" # console or json
55+
56+[logging.file]
57+enabled = true
58+path = "~/.gordon/logs/gordon.log" # Default location
59+max_size = 100 # MB before rotation
60+max_backups = 3 # Old files to keep
61+max_age = 28 # Days to keep
62+
63+[logging.container_logs]
64+enabled = true
65+dir = "~/.gordon/logs/containers" # Default location
66+max_size = 100
67+max_backups = 3
68+max_age = 28
69+
70+# Environment variables
71+[env]
72+dir = "~/.gordon/env" # Default location
73+
74+# Volume settings
75+[volumes]
76+auto_create = true # Auto-create from Dockerfile VOLUME
77+prefix = "gordon" # Volume name prefix
78+preserve = true # Keep volumes on container removal
79+
80+# Network isolation
81+[network_isolation]
82+enabled = true # Per-app isolated networks
83+network_prefix = "gordon" # Network name prefix
84+dns_suffix = ".internal" # DNS suffix for services
85+
86+# Auto-route
87+[auto_route]
88+enabled = false # Auto-create routes from image names
89+
90+# Routes (required)
91+[routes]
92+"app.mydomain.com" = "myapp:latest"
93+"api.mydomain.com" = "myapi:v2.1.0"
94+
95+# Network groups
96+[network_groups]
97+"backend" = ["app.mydomain.com", "api.mydomain.com"]
98+
99+# Attachments
100+[attachments]
101+"app.mydomain.com" = ["postgres:latest", "redis:latest"]
102+"backend" = ["rabbitmq:latest"]
103+```
104+
105+## Configuration Sections
106+
107+| Section | Description | Documentation |
108+|---------|-------------|---------------|
109+| `[server]` | Core server settings | [Server](./server.md) |
110+| `[secrets]` | Secrets backend configuration | [Secrets](./secrets.md) |
111+| `[registry_auth]` | Registry authentication | [Registry Auth](./registry-auth.md) |
112+| `[logging]` | Logging configuration | [Logging](./logging.md) |
113+| `[env]` | Environment variable settings | [Environment](./env.md) |
114+| `[volumes]` | Volume management | [Volumes](./volumes.md) |
115+| `[network_isolation]` | Network isolation settings | [Network Isolation](./network-isolation.md) |
116+| `[auto_route]` | Automatic route creation | [Auto Route](./auto-route.md) |
117+| `[routes]` | Domain to image mapping | [Routes](./routes.md) |
118+| `[network_groups]` | Shared service networks | [Network Groups](./network-groups.md) |
119+| `[attachments]` | Service dependencies | [Attachments](./attachments.md) |
120+
121+## Default Values
122+
123+| Setting | Default |
124+|---------|---------|
125+| `server.port` | `80` |
126+| `server.registry_port` | `5000` |
127+| `server.data_dir` | `~/.gordon` |
128+| `secrets.backend` | `"unsafe"` |
129+| `registry_auth.enabled` | `false` |
130+| `registry_auth.type` | `"password"` |
131+| `registry_auth.token_expiry` | `"720h"` |
132+| `logging.level` | `"info"` |
133+| `logging.format` | `"console"` |
134+| `logging.file.enabled` | `false` |
135+| `logging.file.max_size` | `100` |
136+| `logging.file.max_backups` | `3` |
137+| `logging.file.max_age` | `28` |
138+| `logging.container_logs.enabled` | `true` |
139+| `volumes.auto_create` | `true` |
140+| `volumes.prefix` | `"gordon"` |
141+| `volumes.preserve` | `true` |
142+| `network_isolation.enabled` | `false` |
143+| `auto_route.enabled` | `false` |
144+
145+## Hot Reload
146+
147+Gordon watches the configuration file and reloads automatically when changes are detected. You can also trigger a manual reload:
148+
149+```bash
150+gordon reload
151+```
152+
153+The following settings require a restart to take effect:
154+
155+- `server.port`
156+- `server.registry_port`
157+- `server.data_dir`
158+- `secrets.backend`
159+- `registry_auth` settings
160+
161+## Environment Variable Override
162+
163+Configuration values can be overridden with environment variables:
164+
165+```bash
166+GORDON_SERVER_PORT=8080 gordon start
167+GORDON_LOGGING_LEVEL=debug gordon start
168+```
169+
170+Pattern: `GORDON_SECTION_KEY` (uppercase, underscores instead of dots)
171+
172+## Related
173+
174+- [Server Configuration](./server.md)
175+- [Routes Configuration](./routes.md)
176+- [Registry Authentication](./registry-auth.md)
A docs/config/logging.md +208 -0
@@ -0,0 +1,208 @@
1+# Logging Configuration
2+
3+Configure application and container log collection.
4+
5+## Configuration
6+
7+```toml
8+[logging]
9+level = "info"
10+format = "console"
11+
12+[logging.file]
13+enabled = true
14+path = "~/.gordon/logs/gordon.log"
15+max_size = 100
16+max_backups = 3
17+max_age = 28
18+
19+[logging.container_logs]
20+enabled = true
21+dir = "~/.gordon/logs/containers"
22+max_size = 100
23+max_backups = 3
24+max_age = 28
25+```
26+
27+## Options
28+
29+### General Logging
30+
31+| Option | Type | Default | Description |
32+|--------|------|---------|-------------|
33+| `level` | string | `"info"` | Log level: trace, debug, info, warn, error |
34+| `format` | string | `"console"` | Output format: console or json |
35+
36+### File Logging
37+
38+| Option | Type | Default | Description |
39+|--------|------|---------|-------------|
40+| `file.enabled` | bool | `false` | Enable file-based logging |
41+| `file.path` | string | - | Path to main log file |
42+| `file.max_size` | int | `100` | Max file size in MB before rotation |
43+| `file.max_backups` | int | `3` | Number of old files to keep |
44+| `file.max_age` | int | `28` | Days to keep old files |
45+
46+### Container Logs
47+
48+| Option | Type | Default | Description |
49+|--------|------|---------|-------------|
50+| `container_logs.enabled` | bool | `true` | Collect container stdout/stderr |
51+| `container_logs.dir` | string | `{data_dir}/logs/containers` | Directory for container logs |
52+| `container_logs.max_size` | int | `100` | Max file size in MB |
53+| `container_logs.max_backups` | int | `3` | Old files to keep |
54+| `container_logs.max_age` | int | `28` | Days to keep files |
55+
56+## Log Levels
57+
58+| Level | Description |
59+|-------|-------------|
60+| `trace` | Very detailed debugging |
61+| `debug` | Debug information |
62+| `info` | General information (default) |
63+| `warn` | Warnings |
64+| `error` | Errors only |
65+
66+## Log Directory Structure
67+
68+```
69+~/.gordon/logs/
70+├── gordon.log # Main application logs
71+├── gordon.log.1 # Rotated log
72+├── gordon.log.2.gz # Compressed old log
73+├── proxy.log # HTTP proxy traffic
74+└── containers/
75+ ├── abc123def.log # Container by ID
76+ ├── app_mydomain_com.log # Symlink by domain
77+ └── api_mydomain_com.log
78+```
79+
80+## Log Rotation
81+
82+Gordon uses automatic log rotation:
83+
84+1. **Size-based**: Rotates when file exceeds `max_size` MB
85+2. **Age-based**: Removes files older than `max_age` days
86+3. **Count-based**: Keeps only `max_backups` old files
87+4. **Compression**: Old files are gzip compressed
88+
89+Example with defaults:
90+- Log grows to 100MB → rotated to `gordon.log.1`
91+- After 3 rotations → oldest is compressed
92+- Files older than 28 days → deleted
93+
94+## Examples
95+
96+### Development
97+
98+```toml
99+[logging]
100+level = "debug"
101+format = "console"
102+
103+[logging.file]
104+enabled = true
105+path = "./logs/gordon.log"
106+max_size = 10
107+max_backups = 2
108+max_age = 7
109+
110+[logging.container_logs]
111+enabled = true
112+dir = "./logs/containers"
113+max_size = 10
114+max_backups = 2
115+max_age = 7
116+```
117+
118+### Production
119+
120+```toml
121+[logging]
122+level = "info"
123+format = "json"
124+
125+[logging.file]
126+enabled = true
127+path = "~/.gordon/logs/gordon.log"
128+max_size = 100
129+max_backups = 10
130+max_age = 90
131+
132+[logging.container_logs]
133+enabled = true
134+dir = "~/.gordon/logs/containers"
135+max_size = 100
136+max_backups = 10
137+max_age = 90
138+```
139+
140+### Minimal (Console Only)
141+
142+```toml
143+[logging]
144+level = "info"
145+format = "console"
146+
147+[logging.file]
148+enabled = false
149+
150+[logging.container_logs]
151+enabled = true
152+```
153+
154+## Viewing Logs
155+
156+### Gordon Logs
157+
158+```bash
159+# Using gordon logs command
160+gordon logs -f # Follow logs
161+gordon logs -n 100 # Last 100 lines
162+
163+# Direct file access
164+tail -f ~/.gordon/logs/gordon.log
165+
166+# With journalctl (if using systemd)
167+journalctl --user -u gordon -f
168+```
169+
170+### Container Logs
171+
172+```bash
173+# View container logs
174+tail -f ~/.gordon/logs/containers/app_mydomain_com.log
175+
176+# All container logs
177+ls ~/.gordon/logs/containers/
178+```
179+
180+## Log Format
181+
182+### Console Format
183+
184+```
185+2024-01-15T10:30:00Z INF container deployed domain=app.mydomain.com image=myapp:latest
186+2024-01-15T10:30:01Z INF proxy routing updated route=app.mydomain.com
187+```
188+
189+### JSON Format
190+
191+```json
192+{"level":"info","time":"2024-01-15T10:30:00Z","message":"container deployed","domain":"app.mydomain.com","image":"myapp:latest"}
193+{"level":"info","time":"2024-01-15T10:30:01Z","message":"proxy routing updated","route":"app.mydomain.com"}
194+```
195+
196+## Security
197+
198+Log files are created with secure permissions:
199+- Directories: `0700` (owner only)
200+- Files: `0600` (owner only)
201+
202+Sensitive values (passwords, tokens) are automatically redacted.
203+
204+## Related
205+
206+- [Configuration Overview](./index.md)
207+- [CLI Commands](../cli/index.md)
208+- [Troubleshooting](../reference/troubleshooting.md)
A docs/config/network-groups.md +182 -0
@@ -0,0 +1,182 @@
1+# Network Groups
2+
3+Group multiple applications into shared networks for inter-service communication.
4+
5+## Configuration
6+
7+```toml
8+[network_groups]
9+"backend" = ["app.mydomain.com", "api.mydomain.com"]
10+"monitoring" = ["grafana.mydomain.com", "prometheus.mydomain.com"]
11+```
12+
13+## Syntax
14+
15+```toml
16+[network_groups]
17+"<group-name>" = ["<domain>", "<domain>", ...]
18+```
19+
20+| Component | Description |
21+|-----------|-------------|
22+| `group-name` | Name for the shared network |
23+| `domains` | List of routes that share this network |
24+
25+## How It Works
26+
27+Network groups create shared Docker networks for multiple applications:
28+
29+```toml
30+[network_groups]
31+"backend" = ["app.mydomain.com", "api.mydomain.com"]
32+```
33+
34+Creates a shared network: `gordon-backend`
35+
36+```
37+┌────────────────────────────────────────────────────┐
38+│ gordon-backend │
39+│ │
40+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
41+│ │ App │←──→│ API │←──→│ Postgres │ │
42+│ │ :3000 │ │ :8080 │ │ :5432 │ │
43+│ └──────────┘ └──────────┘ └──────────┘ │
44+│ │
45+└────────────────────────────────────────────────────┘
46+```
47+
48+## Service Discovery
49+
50+Applications in the same group can communicate using container names:
51+
52+```toml
53+[routes]
54+"app.mydomain.com" = "frontend:latest"
55+"api.mydomain.com" = "backend:latest"
56+
57+[network_groups]
58+"services" = ["app.mydomain.com", "api.mydomain.com"]
59+```
60+
61+The frontend can reach the backend:
62+```javascript
63+// In frontend app
64+const response = await fetch("http://backend:8080/api/data");
65+```
66+
67+## Shared Attachments
68+
69+Combine network groups with attachments for shared services:
70+
71+```toml
72+[network_groups]
73+"backend" = ["app.mydomain.com", "api.mydomain.com"]
74+
75+[attachments]
76+"backend" = ["postgres:latest", "redis:latest"]
77+```
78+
79+Both `app.mydomain.com` and `api.mydomain.com` can access:
80+- `postgres:5432`
81+- `redis:6379`
82+
83+## Multiple Groups
84+
85+An application can only be in one network group:
86+
87+```toml
88+# Each app belongs to one group
89+[network_groups]
90+"customer-facing" = ["app.mydomain.com", "api.mydomain.com"]
91+"internal" = ["admin.mydomain.com", "metrics.mydomain.com"]
92+```
93+
94+## Mixed Isolation
95+
96+Combine network groups with per-app attachments:
97+
98+```toml
99+[network_groups]
100+"backend" = ["app.mydomain.com", "api.mydomain.com"]
101+
102+[attachments]
103+# Shared by both apps
104+"backend" = ["redis:latest", "rabbitmq:latest"]
105+
106+# App-specific databases
107+"app.mydomain.com" = ["app-postgres:latest"]
108+"api.mydomain.com" = ["api-postgres:latest"]
109+```
110+
111+Result:
112+- Shared: Redis and RabbitMQ
113+- Isolated: Each app has its own Postgres
114+
115+## Examples
116+
117+### Microservices Architecture
118+
119+```toml
120+[routes]
121+"orders.mydomain.com" = "orders-service:latest"
122+"inventory.mydomain.com" = "inventory-service:latest"
123+"shipping.mydomain.com" = "shipping-service:latest"
124+"payments.mydomain.com" = "payments-service:latest"
125+
126+[network_groups]
127+"order-processing" = ["orders.mydomain.com", "inventory.mydomain.com", "shipping.mydomain.com"]
128+"payment-processing" = ["payments.mydomain.com"]
129+
130+[attachments]
131+"order-processing" = ["rabbitmq:latest"]
132+"payments.mydomain.com" = ["payments-db:latest"]
133+```
134+
135+### Monitoring Stack
136+
137+```toml
138+[routes]
139+"grafana.mydomain.com" = "grafana/grafana:latest"
140+"prometheus.mydomain.com" = "prom/prometheus:latest"
141+"alertmanager.mydomain.com" = "prom/alertmanager:latest"
142+
143+[network_groups]
144+"monitoring" = ["grafana.mydomain.com", "prometheus.mydomain.com", "alertmanager.mydomain.com"]
145+
146+[attachments]
147+"monitoring" = ["prometheus-data:latest"]
148+```
149+
150+### Multi-Tenant SaaS
151+
152+```toml
153+[routes]
154+"app.saas.com" = "saas-app:latest"
155+"api.saas.com" = "saas-api:latest"
156+"admin.saas.com" = "saas-admin:latest"
157+
158+# API and App share backend services
159+[network_groups]
160+"platform" = ["app.saas.com", "api.saas.com"]
161+
162+# Admin is isolated
163+[attachments]
164+"platform" = ["postgres:latest", "redis:latest", "elasticsearch:latest"]
165+"admin.saas.com" = ["admin-db:latest"]
166+```
167+
168+## Network Naming
169+
170+Group networks are named: `{network_prefix}-{group-name}`
171+
172+| Group | Network Name |
173+|-------|--------------|
174+| `backend` | `gordon-backend` |
175+| `monitoring` | `gordon-monitoring` |
176+| `order-processing` | `gordon-order-processing` |
177+
178+## Related
179+
180+- [Network Isolation](./network-isolation.md)
181+- [Attachments](./attachments.md)
182+- [Routes](./routes.md)
A docs/config/network-isolation.md +191 -0
@@ -0,0 +1,191 @@
1+# Network Isolation
2+
3+Isolate applications in separate Docker networks for enhanced security.
4+
5+## Configuration
6+
7+```toml
8+[network_isolation]
9+enabled = true
10+network_prefix = "gordon"
11+dns_suffix = ".internal"
12+```
13+
14+## Options
15+
16+| Option | Type | Default | Description |
17+|--------|------|---------|-------------|
18+| `enabled` | bool | `false` | Enable per-app network isolation |
19+| `network_prefix` | string | `"gordon"` | Prefix for created networks |
20+| `dns_suffix` | string | `".internal"` | DNS suffix for service discovery |
21+
22+## How It Works
23+
24+When network isolation is enabled, each application gets its own Docker network:
25+
26+```
27+[network_isolation]
28+enabled = true
29+network_prefix = "gordon"
30+
31+[routes]
32+"app.mydomain.com" = "myapp:latest"
33+"api.mydomain.com" = "myapi:latest"
34+```
35+
36+Creates two isolated networks:
37+- `gordon-app-mydomain-com`
38+- `gordon-api-mydomain-com`
39+
40+## Network Naming
41+
42+Networks are named: `{prefix}-{domain-with-dashes}`
43+
44+| Domain | Network Name |
45+|--------|--------------|
46+| `app.mydomain.com` | `gordon-app-mydomain-com` |
47+| `api.company.io` | `gordon-api-company-io` |
48+| `staging.app.dev` | `gordon-staging-app-dev` |
49+
50+## Security Benefits
51+
52+### Without Network Isolation
53+
54+All containers can potentially communicate:
55+
56+```
57+┌──────────────────────────────────────────────┐
58+│ Default Bridge Network │
59+│ │
60+│ App A ←──────→ App B ←──────→ App C │
61+│ ↕ ↕ ↕ │
62+│ DB A ←──────→ DB B ←──────→ DB C │
63+│ │
64+└──────────────────────────────────────────────┘
65+```
66+
67+### With Network Isolation
68+
69+Each app is isolated with its dependencies:
70+
71+```
72+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
73+│ gordon-app-a │ │ gordon-app-b │ │ gordon-app-c │
74+│ │ │ │ │ │
75+│ App A ←→ DB A │ │ App B ←→ DB B │ │ App C ←→ DB C │
76+│ │ │ │ │ │
77+└─────────────────┘ └─────────────────┘ └─────────────────┘
78+ ↑ ↑ ↑
79+ └───────── No direct communication ─────┘
80+```
81+
82+## Service Discovery
83+
84+Within an isolated network, services are discoverable by name:
85+
86+```toml
87+[network_isolation]
88+enabled = true
89+
90+[attachments]
91+"app.mydomain.com" = ["postgres:latest", "redis:latest"]
92+```
93+
94+Your application connects using simple hostnames:
95+
96+```python
97+# These work within the isolated network
98+db = connect("postgresql://postgres:5432/mydb")
99+cache = connect("redis://redis:6379")
100+```
101+
102+## DNS Resolution
103+
104+The `dns_suffix` option adds a suffix for internal DNS resolution:
105+
106+```toml
107+[network_isolation]
108+dns_suffix = ".internal"
109+```
110+
111+Services can be accessed as:
112+- `postgres` (short form)
113+- `postgres.internal` (with suffix)
114+
115+## Examples
116+
117+### Basic Isolation
118+
119+```toml
120+[network_isolation]
121+enabled = true
122+
123+[routes]
124+"app.mydomain.com" = "myapp:latest"
125+"api.mydomain.com" = "myapi:latest"
126+
127+[attachments]
128+"app.mydomain.com" = ["app-postgres:latest"]
129+"api.mydomain.com" = ["api-postgres:latest"]
130+```
131+
132+Each app gets its own network with its own database.
133+
134+### Production Configuration
135+
136+```toml
137+[network_isolation]
138+enabled = true
139+network_prefix = "prod"
140+dns_suffix = ".internal"
141+
142+[routes]
143+"app.company.com" = "company-app:v2.1.0"
144+"api.company.com" = "company-api:v1.5.0"
145+"admin.company.com" = "admin-panel:v1.0.0"
146+```
147+
148+Creates networks:
149+- `prod-app-company-com`
150+- `prod-api-company-com`
151+- `prod-admin-company-com`
152+
153+### Shared Services with Network Groups
154+
155+When apps need to communicate, use network groups:
156+
157+```toml
158+[network_isolation]
159+enabled = true
160+
161+[network_groups]
162+"backend" = ["app.mydomain.com", "api.mydomain.com"]
163+
164+[attachments]
165+"backend" = ["shared-postgres:latest", "shared-redis:latest"]
166+```
167+
168+Both apps share the `gordon-backend` network.
169+
170+## Inspecting Networks
171+
172+View created networks:
173+
174+```bash
175+docker network ls | grep gordon
176+# gordon-app-mydomain-com
177+# gordon-api-mydomain-com
178+# gordon-backend
179+```
180+
181+Inspect a network:
182+
183+```bash
184+docker network inspect gordon-app-mydomain-com
185+```
186+
187+## Related
188+
189+- [Network Groups](./network-groups.md)
190+- [Attachments](./attachments.md)
191+- [Configuration Overview](./index.md)
A docs/config/registry-auth.md +201 -0
@@ -0,0 +1,201 @@
1+# Registry Authentication
2+
3+Secure your container registry with password or token-based authentication.
4+
5+## Configuration
6+
7+```toml
8+[registry_auth]
9+enabled = true
10+type = "token" # "password" or "token"
11+
12+# For token auth:
13+token_secret = "gordon/registry/token_secret" # Path in secrets backend
14+token_expiry = "720h" # Duration or 0 for never
15+
16+# For password auth:
17+# username = "deploy"
18+# password_hash = "gordon/registry/password_hash" # Path in secrets backend
19+```
20+
21+## Options
22+
23+| Option | Type | Default | Description |
24+|--------|------|---------|-------------|
25+| `enabled` | bool | `false` | Enable registry authentication |
26+| `type` | string | `"password"` | Auth type: `"password"` or `"token"` |
27+| `username` | string | - | Username for password auth |
28+| `password_hash` | string | - | Path to bcrypt hash in secrets backend |
29+| `token_secret` | string | - | Path to JWT signing secret in secrets backend |
30+| `token_expiry` | string | `"720h"` | Token validity duration (0 = never expires) |
31+
32+## Authentication Types
33+
34+### Token Authentication (Recommended)
35+
36+JWT-based authentication, ideal for CI/CD pipelines:
37+
38+```toml
39+[secrets]
40+backend = "pass"
41+
42+[registry_auth]
43+enabled = true
44+type = "token"
45+token_secret = "gordon/registry/token_secret"
46+token_expiry = "720h" # 30 days, or "0" for never
47+```
48+
49+**Setup:**
50+```bash
51+# Create token secret (random 32+ characters)
52+pass insert gordon/registry/token_secret
53+
54+# Generate a token
55+gordon auth token generate --subject ci-bot --scopes push,pull --expiry 0
56+```
57+
58+**Usage:**
59+```bash
60+# Docker login with token
61+docker login -u ci-bot -p <token> registry.mydomain.com
62+```
63+
64+**Token Management:**
65+```bash
66+# List all tokens
67+gordon auth token list
68+
69+# Revoke a token
70+gordon auth token revoke <token-id>
71+```
72+
73+### Password Authentication
74+
75+Simple username/password authentication:
76+
77+```toml
78+[secrets]
79+backend = "pass"
80+
81+[registry_auth]
82+enabled = true
83+type = "password"
84+username = "deploy"
85+password_hash = "gordon/registry/password_hash"
86+```
87+
88+**Setup:**
89+```bash
90+# Generate bcrypt hash
91+gordon auth password hash
92+# Enter password when prompted
93+
94+# Store hash in secrets backend
95+pass insert gordon/registry/password_hash
96+# Paste the bcrypt hash
97+```
98+
99+**Usage:**
100+```bash
101+docker login -u deploy -p <password> registry.mydomain.com
102+```
103+
104+## Token Scopes
105+
106+Tokens can have different permission levels:
107+
108+| Scope | Permission |
109+|-------|------------|
110+| `push` | Push images to registry |
111+| `pull` | Pull images from registry |
112+| `push,pull` | Both push and pull (default) |
113+
114+```bash
115+# Pull-only token (for read-only access)
116+gordon auth token generate --subject reader --scopes pull --expiry 720h
117+
118+# Push-only token (for CI builds)
119+gordon auth token generate --subject builder --scopes push --expiry 0
120+```
121+
122+## Token Expiry
123+
124+Control how long tokens remain valid:
125+
126+```bash
127+# Never expires (for CI/CD)
128+gordon auth token generate --subject ci --expiry 0
129+
130+# Expires in 30 days
131+gordon auth token generate --subject temp --expiry 720h
132+
133+# Expires in 24 hours
134+gordon auth token generate --subject short --expiry 24h
135+```
136+
137+## Instance-Specific Tokens
138+
139+Tokens are signed with the `token_secret`, which is unique to each Gordon instance. This means:
140+
141+- Tokens from one Gordon instance won't work on another
142+- Changing the `token_secret` invalidates all existing tokens
143+- Each environment (dev, staging, prod) should have different secrets
144+
145+## Examples
146+
147+### CI/CD Setup
148+
149+```toml
150+[secrets]
151+backend = "pass"
152+
153+[registry_auth]
154+enabled = true
155+type = "token"
156+token_secret = "gordon/registry/token_secret"
157+```
158+
159+```bash
160+# Generate CI token
161+gordon auth token generate --subject github-actions --scopes push,pull --expiry 0
162+
163+# Token output:
164+# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
165+```
166+
167+In GitHub Actions:
168+```yaml
169+- name: Login to Gordon Registry
170+ run: docker login -u github-actions -p ${{ secrets.GORDON_TOKEN }} ${{ secrets.GORDON_REGISTRY }}
171+```
172+
173+### Development Setup
174+
175+```toml
176+[registry_auth]
177+enabled = false # Disable for local development
178+```
179+
180+### Production with Password
181+
182+```toml
183+[secrets]
184+backend = "pass"
185+
186+[registry_auth]
187+enabled = true
188+type = "password"
189+username = "deploy"
190+password_hash = "gordon/registry/password_hash"
191+```
192+
193+## Internal Registry Auth
194+
195+Gordon generates internal credentials automatically when auth is enabled. These are used for loopback pulls when deploying containers and are regenerated on each restart.
196+
197+## Related
198+
199+- [Secrets Configuration](./secrets.md)
200+- [CLI Auth Commands](../cli/auth.md)
201+- [GitHub Actions Deployment](../deployment/github-actions.md)
A docs/config/routes.md +201 -0
@@ -0,0 +1,201 @@
1+# Routes Configuration
2+
3+Routes map domains to container images.
4+
5+## Configuration
6+
7+```toml
8+[routes]
9+"app.mydomain.com" = "myapp:latest"
10+"api.mydomain.com" = "myapi:v2.1.0"
11+"admin.mydomain.com" = "admin-panel:v1.0.0"
12+```
13+
14+## Syntax
15+
16+```toml
17+[routes]
18+"<domain>" = "<image>:<tag>"
19+```
20+
21+| Component | Description |
22+|-----------|-------------|
23+| `domain` | Fully qualified domain name |
24+| `image` | Container image name (as pushed to Gordon's registry) |
25+| `tag` | Image tag (version, `latest`, etc.) |
26+
27+## Route Types
28+
29+### HTTPS Routes (Default)
30+
31+Standard routes expect HTTPS traffic (terminated by Cloudflare or similar):
32+
33+```toml
34+[routes]
35+"app.mydomain.com" = "myapp:latest"
36+```
37+
38+### HTTP-Only Routes
39+
40+For internal or development routes without HTTPS:
41+
42+```toml
43+[routes]
44+"http://internal.local" = "internal-app:latest"
45+```
46+
47+The `http://` prefix tells Gordon not to expect HTTPS.
48+
49+## Version Strategies
50+
51+### Latest Tag
52+
53+Always deploy the most recent push:
54+
55+```toml
56+[routes]
57+"app.mydomain.com" = "myapp:latest"
58+```
59+
60+### Pinned Version
61+
62+Deploy a specific version:
63+
64+```toml
65+[routes]
66+"app.mydomain.com" = "myapp:v2.1.0"
67+```
68+
69+Update the config to deploy a new version:
70+
71+```toml
72+[routes]
73+"app.mydomain.com" = "myapp:v2.2.0" # Changed
74+```
75+
76+### Semantic Versioning
77+
78+Use different routes for different versions:
79+
80+```toml
81+[routes]
82+"app.mydomain.com" = "myapp:v2.1.0" # Production
83+"staging.mydomain.com" = "myapp:staging" # Staging
84+"canary.mydomain.com" = "myapp:canary" # Canary
85+```
86+
87+## Multiple Routes
88+
89+### Same Image, Different Domains
90+
91+```toml
92+[routes]
93+"app.mydomain.com" = "myapp:latest"
94+"www.mydomain.com" = "myapp:latest"
95+"mydomain.com" = "myapp:latest"
96+```
97+
98+### Multiple Services
99+
100+```toml
101+[routes]
102+"app.mydomain.com" = "frontend:latest"
103+"api.mydomain.com" = "backend:v2.1.0"
104+"docs.mydomain.com" = "documentation:latest"
105+"status.mydomain.com" = "status-page:v1.0.0"
106+```
107+
108+## How Routing Works
109+
110+1. Request arrives for `app.mydomain.com`
111+2. Gordon looks up the route in configuration
112+3. Finds running container for `myapp:latest`
113+4. Proxies request to container's exposed port
114+5. Returns response to client
115+
116+```
117+Client ─> Gordon Proxy ─> Container
118+ (port 80) (exposed port)
119+```
120+
121+## Deployment Flow
122+
123+When you push an image:
124+
125+```bash
126+docker push registry.mydomain.com/myapp:latest
127+```
128+
129+1. Gordon receives the image
130+2. Checks routes for any that use `myapp:latest`
131+3. Deploys new container for each matching route
132+4. Updates proxy to route traffic to new container
133+5. Stops old container
134+
135+## Hot Reload
136+
137+Routes reload automatically when the config file changes:
138+
139+```bash
140+# Edit config
141+vim ~/.config/gordon/gordon.toml
142+
143+# Add new route
144+[routes]
145+"newapp.mydomain.com" = "newapp:latest"
146+
147+# Save - Gordon reloads automatically
148+```
149+
150+Or trigger manually:
151+
152+```bash
153+gordon reload
154+```
155+
156+## Examples
157+
158+### Development Setup
159+
160+```toml
161+[routes]
162+"app.local" = "myapp:latest"
163+"api.local" = "myapi:latest"
164+```
165+
166+Add to `/etc/hosts`:
167+```
168+127.0.0.1 app.local api.local registry.local
169+```
170+
171+### Production Setup
172+
173+```toml
174+[routes]
175+"app.company.com" = "company-app:v2.1.0"
176+"api.company.com" = "company-api:v1.5.2"
177+"admin.company.com" = "admin-panel:v1.0.1"
178+"docs.company.com" = "company-docs:latest"
179+```
180+
181+### Multi-Tenant SaaS
182+
183+```toml
184+[routes]
185+# Platform services
186+"app.saas-platform.com" = "saas-frontend:v2.1.0"
187+"api.saas-platform.com" = "saas-api:v3.2.1"
188+
189+# Customer subdomains
190+"acme.saas-platform.com" = "saas-app:v2.1.0"
191+"beta.saas-platform.com" = "saas-app:v2.1.0"
192+
193+# Customer custom domains
194+"portal.acme-corp.com" = "saas-app:v2.1.0"
195+```
196+
197+## Related
198+
199+- [Configuration Overview](./index.md)
200+- [Auto Route](./auto-route.md)
201+- [Attachments](./attachments.md)
A docs/config/secrets.md +199 -0
@@ -0,0 +1,199 @@
1+# Secrets Configuration
2+
3+Configure how Gordon stores and retrieves sensitive data.
4+
5+## Configuration
6+
7+```toml
8+[secrets]
9+backend = "pass" # "pass", "sops", or "unsafe"
10+```
11+
12+## Options
13+
14+| Option | Type | Default | Description |
15+|--------|------|---------|-------------|
16+| `backend` | string | `"unsafe"` | Secrets storage backend |
17+
18+## Backends
19+
20+### Pass (Recommended for Production)
21+
22+Uses the Unix password manager (`pass`) for secure secret storage:
23+
24+```toml
25+[secrets]
26+backend = "pass"
27+```
28+
29+**Setup:**
30+```bash
31+# Install pass
32+sudo apt install pass
33+
34+# Initialize with GPG key
35+pass init your-gpg-key-id
36+
37+# Store secrets
38+pass insert gordon/registry/password_hash
39+pass insert gordon/registry/token_secret
40+```
41+
42+**Usage in config:**
43+```toml
44+[registry_auth]
45+password_hash = "gordon/registry/password_hash" # Path in pass store
46+token_secret = "gordon/registry/token_secret"
47+```
48+
49+**Benefits:**
50+- GPG-encrypted storage
51+- Version control friendly (encrypted files)
52+- Standard Unix tooling
53+- Works with team GPG keys
54+
55+### SOPS
56+
57+Uses Mozilla SOPS for encrypted file-based secrets:
58+
59+```toml
60+[secrets]
61+backend = "sops"
62+```
63+
64+**Setup:**
65+```bash
66+# Install sops
67+brew install sops # macOS
68+# or download from https://github.com/getsops/sops/releases
69+
70+# Create encrypted secrets file
71+sops secrets.yaml
72+```
73+
74+**Usage in env files:**
75+```bash
76+# ~/.gordon/env/app_mydomain_com.env
77+API_SECRET=${sops:secrets.yaml:api.secret}
78+DB_PASSWORD=${sops:secrets.yaml:database.password}
79+```
80+
81+**Benefits:**
82+- Multiple encryption backends (AWS KMS, GCP KMS, Azure Key Vault, PGP)
83+- YAML/JSON file encryption
84+- Git-friendly (encrypted files in repo)
85+
86+### Unsafe (Development Only)
87+
88+Stores secrets as plain text files:
89+
90+```toml
91+[secrets]
92+backend = "unsafe"
93+```
94+
95+**Storage location:**
96+```
97+{data_dir}/secrets/
98+├── gordon/
99+│ └── registry/
100+│ ├── password_hash
101+│ └── token_secret
102+```
103+
104+**Usage:**
105+```bash
106+# Create secret
107+mkdir -p ~/.gordon/secrets/gordon/registry
108+echo "your-bcrypt-hash" > ~/.gordon/secrets/gordon/registry/password_hash
109+```
110+
111+> **Warning:** Only use for local development. Secrets are stored in plain text.
112+
113+## Secret Provider Syntax
114+
115+In environment files, reference secrets using provider syntax:
116+
117+### Pass Provider
118+
119+```bash
120+# ${pass:<path>}
121+DATABASE_PASSWORD=${pass:myapp/database/password}
122+API_KEY=${pass:myapp/api-key}
123+```
124+
125+### SOPS Provider
126+
127+```bash
128+# ${sops:<file>:<key.path>}
129+DATABASE_PASSWORD=${sops:secrets.yaml:database.password}
130+API_SECRET=${sops:production.yaml:api.secret.key}
131+```
132+
133+## Examples
134+
135+### Production with Pass
136+
137+```toml
138+[secrets]
139+backend = "pass"
140+
141+[registry_auth]
142+enabled = true
143+type = "token"
144+token_secret = "gordon/registry/token_secret"
145+```
146+
147+```bash
148+# Setup
149+pass insert gordon/registry/token_secret
150+# Enter a random 32+ character string
151+
152+# Generate tokens
153+gordon auth token generate --subject deploy --expiry 0
154+```
155+
156+### Development with Unsafe
157+
158+```toml
159+[secrets]
160+backend = "unsafe"
161+
162+[registry_auth]
163+enabled = false
164+```
165+
166+### Enterprise with SOPS
167+
168+```toml
169+[secrets]
170+backend = "sops"
171+
172+[registry_auth]
173+enabled = true
174+type = "token"
175+token_secret = "gordon/registry/token_secret"
176+```
177+
178+Environment file:
179+```bash
180+# ~/.gordon/env/app_company_com.env
181+NODE_ENV=production
182+DATABASE_URL=postgresql://db:5432/app
183+DATABASE_PASSWORD=${sops:secrets.yaml:database.password}
184+API_KEY=${sops:secrets.yaml:api.key}
185+JWT_SECRET=${sops:secrets.yaml:jwt.secret}
186+```
187+
188+## Security Recommendations
189+
190+1. **Production**: Always use `pass` or `sops` backend
191+2. **Never commit**: Don't commit unencrypted secrets to git
192+3. **Rotate regularly**: Regenerate tokens and passwords periodically
193+4. **Least privilege**: Use separate secrets per environment
194+
195+## Related
196+
197+- [Registry Authentication](./registry-auth.md)
198+- [Environment Variables](./env.md)
199+- [Configuration Overview](./index.md)
A docs/config/server.md +144 -0
@@ -0,0 +1,144 @@
1+# Server Configuration
2+
3+Core server settings for Gordon.
4+
5+## Configuration
6+
7+```toml
8+[server]
9+port = 8080 # HTTP proxy port
10+registry_port = 5000 # Container registry port
11+registry_domain = "registry.mydomain.com" # Registry domain (required)
12+# data_dir = "~/.gordon" # Data storage directory (default)
13+```
14+
15+## Options
16+
17+| Option | Type | Default | Description |
18+|--------|------|---------|-------------|
19+| `port` | int | `80` | HTTP proxy port for routing traffic to containers |
20+| `registry_port` | int | `5000` | Docker registry port for image push/pull |
21+| `registry_domain` | string | **required** | Domain for the container registry |
22+| `data_dir` | string | `~/.gordon` | Directory for registry data, logs, and env files |
23+
24+## Port Configuration
25+
26+### Proxy Port
27+
28+The `port` setting controls where Gordon listens for HTTP traffic:
29+
30+```toml
31+[server]
32+port = 8080 # Use 8080 for rootless containers with port forwarding
33+```
34+
35+For rootless containers, you'll typically use a high port and configure firewall port forwarding:
36+
37+```bash
38+# Forward port 80 to 8080
39+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
40+```
41+
42+### Registry Port
43+
44+The `registry_port` setting controls where Gordon accepts image pushes:
45+
46+```toml
47+[server]
48+registry_port = 5000
49+```
50+
51+Docker/Podman clients push to this port:
52+
53+```bash
54+docker push registry.mydomain.com:5000/myapp:latest
55+```
56+
57+## Registry Domain
58+
59+The `registry_domain` is required and must match your DNS configuration:
60+
61+```toml
62+[server]
63+registry_domain = "registry.mydomain.com"
64+```
65+
66+This domain is used for:
67+- Docker login authentication
68+- Image push/pull operations
69+- Internal registry references
70+
71+## Data Directory
72+
73+The `data_dir` setting controls where Gordon stores data:
74+
75+```toml
76+[server]
77+data_dir = "~/.gordon" # Default for user installations
78+```
79+
80+### Directory Structure
81+
82+```
83+~/.gordon/
84+├── registry/ # Container images and manifests
85+│ ├── blobs/
86+│ └── manifests/
87+├── env/ # Environment files per domain
88+│ ├── app_mydomain_com.env
89+│ └── api_mydomain_com.env
90+├── logs/ # Application and container logs
91+│ ├── gordon.log
92+│ ├── proxy.log
93+│ └── containers/
94+└── secrets/ # Secrets (unsafe backend only)
95+```
96+
97+### Permissions
98+
99+Gordon creates directories with secure permissions:
100+- Directories: `0700` (owner only)
101+- Files: `0600` (owner only)
102+
103+## Examples
104+
105+### Development Configuration
106+
107+```toml
108+[server]
109+port = 8080
110+registry_port = 5000
111+registry_domain = "registry.local"
112+data_dir = "./dev-data"
113+```
114+
115+### Production Configuration
116+
117+```toml
118+[server]
119+port = 8080
120+registry_port = 5000
121+registry_domain = "registry.company.com"
122+data_dir = "/var/lib/gordon"
123+```
124+
125+### Rootless Container Setup
126+
127+```toml
128+[server]
129+port = 8080 # High port for rootless
130+registry_port = 5000
131+registry_domain = "registry.mydomain.com"
132+```
133+
134+With firewall port forwarding:
135+```bash
136+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
137+sudo firewall-cmd --reload
138+```
139+
140+## Related
141+
142+- [Configuration Overview](./index.md)
143+- [Installation Guide](../installation.md)
144+- [Routes Configuration](./routes.md)
A docs/config/volumes.md +165 -0
@@ -0,0 +1,165 @@
1+# Volumes Configuration
2+
3+Configure automatic persistent storage for containers.
4+
5+## Configuration
6+
7+```toml
8+[volumes]
9+auto_create = true
10+prefix = "gordon"
11+preserve = true
12+```
13+
14+## Options
15+
16+| Option | Type | Default | Description |
17+|--------|------|---------|-------------|
18+| `auto_create` | bool | `true` | Automatically create volumes from Dockerfile VOLUME |
19+| `prefix` | string | `"gordon"` | Prefix for volume names |
20+| `preserve` | bool | `true` | Keep volumes when containers are removed |
21+
22+## How It Works
23+
24+Gordon automatically creates Docker volumes from Dockerfile `VOLUME` directives:
25+
26+```dockerfile
27+FROM postgres:15
28+VOLUME ["/var/lib/postgresql/data"]
29+```
30+
31+When Gordon deploys this container:
32+
33+1. Reads `VOLUME` directives from image metadata
34+2. Creates named volumes with `prefix-domain-path` naming
35+3. Mounts volumes to the container
36+4. Preserves data across container updates
37+
38+## Volume Naming
39+
40+Volumes are named: `{prefix}-{domain}-{path}`
41+
42+| Domain | Volume Path | Volume Name |
43+|--------|-------------|-------------|
44+| `app.mydomain.com` | `/data` | `gordon-app-mydomain-com-data` |
45+| `db.mydomain.com` | `/var/lib/postgresql/data` | `gordon-db-mydomain-com-var-lib-postgresql-data` |
46+
47+## Persistence
48+
49+### Default: Preserve Volumes
50+
51+```toml
52+[volumes]
53+preserve = true
54+```
55+
56+With `preserve = true`:
57+- Volumes persist when containers are updated
58+- Data survives container restarts
59+- Volumes remain even if container is removed
60+
61+### Remove with Container
62+
63+```toml
64+[volumes]
65+preserve = false
66+```
67+
68+With `preserve = false`:
69+- Volumes are removed when containers are removed
70+- Useful for stateless containers
71+- Frees up disk space automatically
72+
73+## Examples
74+
75+### Database Container
76+
77+```dockerfile
78+# my-postgres.Dockerfile
79+FROM postgres:15
80+VOLUME ["/var/lib/postgresql/data"]
81+ENV POSTGRES_DB=myapp
82+ENV POSTGRES_USER=app
83+```
84+
85+Gordon automatically:
86+- Creates `gordon-db-mydomain-com-var-lib-postgresql-data` volume
87+- Mounts it to `/var/lib/postgresql/data`
88+- Preserves data across postgres container updates
89+
90+### Application with Uploads
91+
92+```dockerfile
93+FROM node:18
94+WORKDIR /app
95+VOLUME ["/app/uploads", "/app/data"]
96+COPY . .
97+CMD ["npm", "start"]
98+```
99+
100+Creates two volumes:
101+- `gordon-app-mydomain-com-app-uploads`
102+- `gordon-app-mydomain-com-app-data`
103+
104+### Custom Prefix
105+
106+```toml
107+[volumes]
108+prefix = "prod"
109+```
110+
111+Volume names become:
112+- `prod-app-mydomain-com-data`
113+- `prod-db-mydomain-com-var-lib-postgresql-data`
114+
115+## Managing Volumes
116+
117+### List Volumes
118+
119+```bash
120+docker volume ls | grep gordon
121+```
122+
123+### Inspect Volume
124+
125+```bash
126+docker volume inspect gordon-app-mydomain-com-data
127+```
128+
129+### Backup Volume
130+
131+```bash
132+docker run --rm \
133+ -v gordon-db-mydomain-com-var-lib-postgresql-data:/data \
134+ -v $(pwd):/backup \
135+ alpine tar -czf /backup/db-backup.tar.gz -C /data .
136+```
137+
138+### Restore Volume
139+
140+```bash
141+docker run --rm \
142+ -v gordon-db-mydomain-com-var-lib-postgresql-data:/data \
143+ -v $(pwd):/backup \
144+ alpine tar -xzf /backup/db-backup.tar.gz -C /data
145+```
146+
147+## Volume Cleanup
148+
149+If you have orphaned volumes:
150+
151+```bash
152+# List all gordon volumes
153+docker volume ls -f name=gordon
154+
155+# Remove specific volume (warning: deletes data!)
156+docker volume rm gordon-old-app-data
157+
158+# Prune unused volumes (be careful!)
159+docker volume prune
160+```
161+
162+## Related
163+
164+- [Attachments](./attachments.md)
165+- [Configuration Overview](./index.md)
A docs/deployment/github-actions.md +282 -0
@@ -0,0 +1,282 @@
1+# GitHub Actions Deployment
2+
3+Automated deployment with GitHub Actions using Gordon's official action.
4+
5+## Prerequisites
6+
7+1. Gordon server running with registry authentication enabled
8+2. Generated deployment token
9+3. GitHub repository secrets configured
10+
11+## Quick Setup
12+
13+### 1. Generate Deployment Token
14+
15+On your Gordon server:
16+
17+```bash
18+gordon auth token generate --subject github-actions --scopes push,pull --expiry 0
19+```
20+
21+Save the token output.
22+
23+### 2. Add GitHub Secrets
24+
25+In your repository: Settings → Secrets → Actions → New repository secret
26+
27+| Secret | Value |
28+|--------|-------|
29+| `GORDON_REGISTRY` | `registry.mydomain.com` |
30+| `GORDON_USERNAME` | `github-actions` |
31+| `GORDON_TOKEN` | The generated token |
32+
33+### 3. Create Workflow
34+
35+Create `.github/workflows/deploy.yml`:
36+
37+```yaml
38+name: Deploy to Gordon
39+
40+on:
41+ push:
42+ tags:
43+ - 'v*'
44+
45+jobs:
46+ deploy:
47+ runs-on: ubuntu-latest
48+ steps:
49+ - uses: actions/checkout@v4
50+
51+ - name: Deploy to Gordon
52+ uses: bnema/gordon/.github/actions/deploy@main
53+ with:
54+ registry: ${{ secrets.GORDON_REGISTRY }}
55+ username: ${{ secrets.GORDON_USERNAME }}
56+ password: ${{ secrets.GORDON_TOKEN }}
57+```
58+
59+### 4. Deploy
60+
61+```bash
62+git tag v1.0.0
63+git push origin v1.0.0
64+```
65+
66+## Action Reference
67+
68+### Inputs
69+
70+| Input | Required | Default | Description |
71+|-------|----------|---------|-------------|
72+| `registry` | Yes | - | Gordon registry URL |
73+| `username` | Yes | - | Registry username |
74+| `password` | Yes | - | Registry token |
75+| `image` | No | repo name | Image name |
76+| `tag` | No | git tag/SHA | Image tag |
77+| `dockerfile` | No | `./Dockerfile` | Dockerfile path |
78+| `context` | No | `.` | Build context |
79+| `push-latest` | No | `false` | Also push :latest tag |
80+| `platforms` | No | `linux/amd64` | Target platforms |
81+| `build-args` | No | - | Build arguments |
82+
83+### Outputs
84+
85+| Output | Description |
86+|--------|-------------|
87+| `image` | Full image name |
88+| `tag` | Image tag |
89+
90+## Workflow Examples
91+
92+### Deploy on Tag Push
93+
94+```yaml
95+name: Deploy to Gordon
96+
97+on:
98+ push:
99+ tags:
100+ - 'v*'
101+
102+jobs:
103+ deploy:
104+ runs-on: ubuntu-latest
105+ steps:
106+ - uses: actions/checkout@v4
107+
108+ - name: Deploy to Gordon
109+ uses: bnema/gordon/.github/actions/deploy@main
110+ with:
111+ registry: ${{ secrets.GORDON_REGISTRY }}
112+ username: ${{ secrets.GORDON_USERNAME }}
113+ password: ${{ secrets.GORDON_TOKEN }}
114+```
115+
116+### Continuous Deployment
117+
118+Deploy on every push to main:
119+
120+```yaml
121+name: Deploy to Gordon
122+
123+on:
124+ push:
125+ branches:
126+ - main
127+
128+jobs:
129+ deploy:
130+ runs-on: ubuntu-latest
131+ steps:
132+ - uses: actions/checkout@v4
133+
134+ - name: Deploy to Gordon
135+ uses: bnema/gordon/.github/actions/deploy@main
136+ with:
137+ registry: ${{ secrets.GORDON_REGISTRY }}
138+ username: ${{ secrets.GORDON_USERNAME }}
139+ password: ${{ secrets.GORDON_TOKEN }}
140+ push-latest: 'true'
141+```
142+
143+### Manual Deployment
144+
145+Allow manual deployments with custom tag:
146+
147+```yaml
148+name: Deploy to Gordon
149+
150+on:
151+ workflow_dispatch:
152+ inputs:
153+ tag:
154+ description: 'Tag to deploy'
155+ required: false
156+
157+jobs:
158+ deploy:
159+ runs-on: ubuntu-latest
160+ steps:
161+ - uses: actions/checkout@v4
162+ with:
163+ ref: ${{ github.event.inputs.tag || github.ref }}
164+
165+ - name: Deploy to Gordon
166+ uses: bnema/gordon/.github/actions/deploy@main
167+ with:
168+ registry: ${{ secrets.GORDON_REGISTRY }}
169+ username: ${{ secrets.GORDON_USERNAME }}
170+ password: ${{ secrets.GORDON_TOKEN }}
171+ tag: ${{ github.event.inputs.tag }}
172+```
173+
174+### Monorepo Deployment
175+
176+Deploy multiple services:
177+
178+```yaml
179+name: Deploy Services
180+
181+on:
182+ push:
183+ tags:
184+ - 'v*'
185+
186+jobs:
187+ deploy-api:
188+ runs-on: ubuntu-latest
189+ steps:
190+ - uses: actions/checkout@v4
191+ - uses: bnema/gordon/.github/actions/deploy@main
192+ with:
193+ registry: ${{ secrets.GORDON_REGISTRY }}
194+ username: ${{ secrets.GORDON_USERNAME }}
195+ password: ${{ secrets.GORDON_TOKEN }}
196+ image: myapp-api
197+ dockerfile: ./services/api/Dockerfile
198+ context: ./services/api
199+
200+ deploy-web:
201+ runs-on: ubuntu-latest
202+ steps:
203+ - uses: actions/checkout@v4
204+ - uses: bnema/gordon/.github/actions/deploy@main
205+ with:
206+ registry: ${{ secrets.GORDON_REGISTRY }}
207+ username: ${{ secrets.GORDON_USERNAME }}
208+ password: ${{ secrets.GORDON_TOKEN }}
209+ image: myapp-web
210+ dockerfile: ./services/web/Dockerfile
211+ context: ./services/web
212+```
213+
214+### With Build Arguments
215+
216+```yaml
217+- uses: bnema/gordon/.github/actions/deploy@main
218+ with:
219+ registry: ${{ secrets.GORDON_REGISTRY }}
220+ username: ${{ secrets.GORDON_USERNAME }}
221+ password: ${{ secrets.GORDON_TOKEN }}
222+ build-args: |
223+ NODE_ENV=production
224+ API_URL=https://api.example.com
225+ BUILD_DATE=${{ github.event.head_commit.timestamp }}
226+```
227+
228+### Multi-Platform Build
229+
230+```yaml
231+- uses: bnema/gordon/.github/actions/deploy@main
232+ with:
233+ registry: ${{ secrets.GORDON_REGISTRY }}
234+ username: ${{ secrets.GORDON_USERNAME }}
235+ password: ${{ secrets.GORDON_TOKEN }}
236+ platforms: linux/amd64,linux/arm64
237+```
238+
239+## Deployment Summary
240+
241+Add a deployment summary to your workflow:
242+
243+```yaml
244+- name: Deploy to Gordon
245+ id: deploy
246+ uses: bnema/gordon/.github/actions/deploy@main
247+ with:
248+ registry: ${{ secrets.GORDON_REGISTRY }}
249+ username: ${{ secrets.GORDON_USERNAME }}
250+ password: ${{ secrets.GORDON_TOKEN }}
251+
252+- name: Deployment Summary
253+ run: |
254+ echo "## Deployment Complete" >> $GITHUB_STEP_SUMMARY
255+ echo "" >> $GITHUB_STEP_SUMMARY
256+ echo "**Image:** \`${{ steps.deploy.outputs.image }}\`" >> $GITHUB_STEP_SUMMARY
257+ echo "**Tag:** \`${{ steps.deploy.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
258+```
259+
260+## Troubleshooting
261+
262+### Authentication Failed
263+
264+```
265+Error: unauthorized: authentication required
266+```
267+
268+**Solution:** Verify secrets are correctly set and token has `push` scope.
269+
270+### Image Not Found After Push
271+
272+**Solution:** Ensure Gordon config has a route for the pushed image.
273+
274+### Build Fails
275+
276+**Solution:** Check Dockerfile path and build context settings.
277+
278+## Related
279+
280+- [Registry Authentication](../config/registry-auth.md)
281+- [Deployment Overview](./index.md)
282+- [Rollback](./rollback.md)
A docs/deployment/index.md +185 -0
@@ -0,0 +1,185 @@
1+# Deployment Overview
2+
3+Strategies for deploying applications with Gordon.
4+
5+## Deployment Methods
6+
7+| Method | Use Case |
8+|--------|----------|
9+| Manual Push | Development, one-off deploys |
10+| GitHub Actions | Automated CI/CD |
11+| GitLab CI | Automated CI/CD |
12+| Generic CI | Any CI/CD system |
13+
14+## Basic Deployment Workflow
15+
16+### 1. Build Locally
17+
18+```bash
19+docker build -t myapp .
20+```
21+
22+### 2. Tag for Registry
23+
24+```bash
25+docker tag myapp registry.mydomain.com/myapp:latest
26+```
27+
28+### 3. Push to Deploy
29+
30+```bash
31+docker push registry.mydomain.com/myapp:latest
32+```
33+
34+Gordon automatically:
35+1. Receives the image
36+2. Deploys a new container
37+3. Routes traffic to it
38+4. Stops the old container
39+
40+## Version Strategies
41+
42+### Latest Tag
43+
44+Always deploy the most recent build:
45+
46+```bash
47+docker tag myapp registry.mydomain.com/myapp:latest
48+docker push registry.mydomain.com/myapp:latest
49+```
50+
51+Configure route:
52+```toml
53+[routes]
54+"app.mydomain.com" = "myapp:latest"
55+```
56+
57+### Semantic Versioning
58+
59+Pin to specific versions:
60+
61+```bash
62+docker tag myapp registry.mydomain.com/myapp:v2.1.0
63+docker push registry.mydomain.com/myapp:v2.1.0
64+```
65+
66+Configure route:
67+```toml
68+[routes]
69+"app.mydomain.com" = "myapp:v2.1.0"
70+```
71+
72+Update config to deploy new version:
73+```toml
74+[routes]
75+"app.mydomain.com" = "myapp:v2.2.0"
76+```
77+
78+### Git SHA Tags
79+
80+Tag with commit hash for traceability:
81+
82+```bash
83+VERSION=$(git rev-parse --short HEAD)
84+docker tag myapp registry.mydomain.com/myapp:$VERSION
85+docker push registry.mydomain.com/myapp:$VERSION
86+```
87+
88+## Zero-Downtime Deployment
89+
90+Gordon performs zero-downtime deployments by default:
91+
92+1. **New container starts** while old is still running
93+2. **Health check** waits for new container readiness
94+3. **Traffic switches** to new container
95+4. **Old container stops** after traffic moves
96+
97+```
98+Timeline ─────────────────────────────────────────>
99+
100+Old Container: [═══════════════════]
101+ ↓ stop
102+New Container: [═════════════════════════>
103+ ↑ start ↑ traffic routed
104+```
105+
106+## Environment-Specific Deployments
107+
108+### Development
109+
110+```toml
111+[routes]
112+"dev.mydomain.com" = "myapp:latest"
113+```
114+
115+```bash
116+docker push registry.mydomain.com/myapp:latest
117+```
118+
119+### Staging
120+
121+```toml
122+[routes]
123+"staging.mydomain.com" = "myapp:staging"
124+```
125+
126+```bash
127+docker tag myapp registry.mydomain.com/myapp:staging
128+docker push registry.mydomain.com/myapp:staging
129+```
130+
131+### Production
132+
133+```toml
134+[routes]
135+"app.mydomain.com" = "myapp:v2.1.0"
136+```
137+
138+```bash
139+docker tag myapp registry.mydomain.com/myapp:v2.1.0
140+docker push registry.mydomain.com/myapp:v2.1.0
141+# Then update gordon.toml and reload
142+```
143+
144+## Deployment Checklist
145+
146+Before deploying:
147+
148+- [ ] Build passes locally
149+- [ ] Tests pass
150+- [ ] Environment file configured (`~/.gordon/env/app_mydomain_com.env`)
151+- [ ] DNS pointing to Gordon server
152+- [ ] Route configured in `gordon.toml`
153+
154+After deploying:
155+
156+- [ ] Container starts successfully
157+- [ ] Application responds to requests
158+- [ ] Logs show no errors
159+- [ ] Health checks pass
160+
161+## Monitoring Deployments
162+
163+### Watch Logs
164+
165+```bash
166+gordon logs -f
167+```
168+
169+### Check Container Status
170+
171+```bash
172+docker ps | grep gordon
173+```
174+
175+### Verify Routing
176+
177+```bash
178+curl -I https://app.mydomain.com
179+```
180+
181+## Related
182+
183+- [GitHub Actions](./github-actions.md)
184+- [Rollback](./rollback.md)
185+- [Routes Configuration](../config/routes.md)
A docs/deployment/rollback.md +208 -0
@@ -0,0 +1,208 @@
1+# Rollback
2+
3+Roll back to previous versions when deployments fail.
4+
5+## Rollback Strategies
6+
7+### 1. Config-Based Rollback
8+
9+Update the route to point to the previous version:
10+
11+```toml
12+# Current (broken)
13+[routes]
14+"app.mydomain.com" = "myapp:v2.1.0"
15+
16+# Rollback to previous
17+[routes]
18+"app.mydomain.com" = "myapp:v2.0.0"
19+```
20+
21+Then reload:
22+
23+```bash
24+gordon reload
25+```
26+
27+### 2. Push Previous Version
28+
29+Re-push the previous image with the `latest` tag:
30+
31+```bash
32+docker pull registry.mydomain.com/myapp:v2.0.0
33+docker tag registry.mydomain.com/myapp:v2.0.0 registry.mydomain.com/myapp:latest
34+docker push registry.mydomain.com/myapp:latest
35+```
36+
37+### 3. Manifest-Based Rollback
38+
39+Use OCI manifest annotations for version control:
40+
41+```bash
42+# Rollback to v2.0.0
43+export VERSION=v2.0.0
44+podman manifest create myapp:latest --amend
45+podman manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
46+podman manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
47+podman manifest push myapp:latest registry.mydomain.com/myapp:latest
48+```
49+
50+## Version Management
51+
52+### Keep Previous Versions
53+
54+Always push versioned tags alongside `latest`:
55+
56+```bash
57+VERSION=$(git describe --tags)
58+docker tag myapp registry.mydomain.com/myapp:$VERSION
59+docker tag myapp registry.mydomain.com/myapp:latest
60+docker push registry.mydomain.com/myapp:$VERSION
61+docker push registry.mydomain.com/myapp:latest
62+```
63+
64+### Semantic Versioning
65+
66+Use semantic versions for clear rollback targets:
67+
68+```
69+v2.1.0 ← Current (broken)
70+v2.0.0 ← Rollback target
71+v1.9.0 ← Older stable
72+```
73+
74+### Git SHA Tags
75+
76+Tag with commit SHA for precise rollbacks:
77+
78+```bash
79+# Deploy
80+SHA=$(git rev-parse --short HEAD)
81+docker push registry.mydomain.com/myapp:$SHA
82+
83+# Rollback to specific commit
84+docker pull registry.mydomain.com/myapp:abc1234
85+docker tag registry.mydomain.com/myapp:abc1234 registry.mydomain.com/myapp:latest
86+docker push registry.mydomain.com/myapp:latest
87+```
88+
89+## Rollback Workflow
90+
91+### Quick Rollback
92+
93+```bash
94+# 1. Identify last working version
95+docker image ls registry.mydomain.com/myapp
96+
97+# 2. Tag as latest
98+docker tag registry.mydomain.com/myapp:v2.0.0 registry.mydomain.com/myapp:latest
99+
100+# 3. Push
101+docker push registry.mydomain.com/myapp:latest
102+```
103+
104+### Config Rollback
105+
106+```bash
107+# 1. Edit config
108+vim ~/.config/gordon/gordon.toml
109+
110+# 2. Change version
111+# "app.mydomain.com" = "myapp:v2.0.0"
112+
113+# 3. Reload
114+gordon reload
115+```
116+
117+## Automated Rollback
118+
119+### GitHub Actions
120+
121+Add rollback capability to your workflow:
122+
123+```yaml
124+name: Rollback
125+
126+on:
127+ workflow_dispatch:
128+ inputs:
129+ version:
130+ description: 'Version to rollback to (e.g., v2.0.0)'
131+ required: true
132+
133+jobs:
134+ rollback:
135+ runs-on: ubuntu-latest
136+ steps:
137+ - name: Login to Registry
138+ run: |
139+ echo "${{ secrets.GORDON_TOKEN }}" | \
140+ docker login -u ${{ secrets.GORDON_USERNAME }} --password-stdin ${{ secrets.GORDON_REGISTRY }}
141+
142+ - name: Rollback
143+ run: |
144+ docker pull ${{ secrets.GORDON_REGISTRY }}/myapp:${{ github.event.inputs.version }}
145+ docker tag ${{ secrets.GORDON_REGISTRY }}/myapp:${{ github.event.inputs.version }} \
146+ ${{ secrets.GORDON_REGISTRY }}/myapp:latest
147+ docker push ${{ secrets.GORDON_REGISTRY }}/myapp:latest
148+
149+ - name: Summary
150+ run: |
151+ echo "## Rollback Complete" >> $GITHUB_STEP_SUMMARY
152+ echo "Rolled back to version: ${{ github.event.inputs.version }}" >> $GITHUB_STEP_SUMMARY
153+```
154+
155+### Rollback Script
156+
157+Create a local rollback script:
158+
159+```bash
160+#!/bin/bash
161+# rollback.sh
162+
163+REGISTRY="registry.mydomain.com"
164+IMAGE="myapp"
165+VERSION=$1
166+
167+if [ -z "$VERSION" ]; then
168+ echo "Usage: ./rollback.sh <version>"
169+ echo "Available versions:"
170+ docker image ls "$REGISTRY/$IMAGE" --format "{{.Tag}}"
171+ exit 1
172+fi
173+
174+echo "Rolling back to $IMAGE:$VERSION..."
175+docker pull "$REGISTRY/$IMAGE:$VERSION"
176+docker tag "$REGISTRY/$IMAGE:$VERSION" "$REGISTRY/$IMAGE:latest"
177+docker push "$REGISTRY/$IMAGE:latest"
178+echo "Rollback complete!"
179+```
180+
181+## Verifying Rollback
182+
183+After rollback:
184+
185+```bash
186+# Check container is running correct version
187+docker inspect gordon-app-mydomain-com | grep Image
188+
189+# Check application responds
190+curl -I https://app.mydomain.com
191+
192+# Check logs
193+gordon logs -f
194+```
195+
196+## Best Practices
197+
198+1. **Always tag versions** - Don't rely solely on `latest`
199+2. **Keep N previous versions** - Maintain rollback options
200+3. **Test before deploy** - Reduce need for rollbacks
201+4. **Document known-good versions** - Track stable releases
202+5. **Automate rollback** - Reduce time to recovery
203+
204+## Related
205+
206+- [Deployment Overview](./index.md)
207+- [GitHub Actions](./github-actions.md)
208+- [Routes Configuration](../config/routes.md)
A docs/getting-started.md +130 -0
@@ -0,0 +1,130 @@
1+# Getting Started
2+
3+Deploy your first app with Gordon in under 5 minutes.
4+
5+## Prerequisites
6+
7+- A Linux VPS with Docker or Podman installed
8+- A domain pointing to your VPS (DNS A record)
9+- Cloudflare account (free tier) for HTTPS termination
10+
11+## 1. Install Gordon
12+
13+```bash
14+# Download the latest release (choose your architecture)
15+# For x86_64:
16+wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
17+tar -xzf gordon_linux_amd64.tar.gz
18+
19+# For ARM64 (Raspberry Pi 4, AWS Graviton, etc.):
20+# wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_arm64.tar.gz
21+# tar -xzf gordon_linux_arm64.tar.gz
22+
23+chmod +x gordon
24+sudo mv gordon /usr/local/bin/
25+```
26+
27+## 2. Start Gordon
28+
29+```bash
30+# First run creates the default config
31+gordon start
32+# Press Ctrl+C to stop
33+```
34+
35+Config is created at `~/.config/gordon/gordon.toml`.
36+
37+## 3. Configure Your Registry Domain
38+
39+Edit `~/.config/gordon/gordon.toml`:
40+
41+```toml
42+[server]
43+port = 8080 # Proxy port (use with Cloudflare)
44+registry_port = 5000 # Registry port
45+registry_domain = "registry.mydomain.com" # Your registry domain
46+
47+[routes]
48+"app.mydomain.com" = "myapp:latest" # Domain → Image mapping
49+```
50+
51+## 4. Set Up DNS
52+
53+In Cloudflare (or your DNS provider):
54+
55+| Type | Name | Content |
56+|------|------|---------|
57+| A | `app` | `YOUR_SERVER_IP` |
58+| A | `registry` | `YOUR_SERVER_IP` |
59+
60+Enable Cloudflare proxy (orange cloud) for HTTPS.
61+
62+## 5. Start Gordon as a Service
63+
64+```bash
65+# Create systemd user service
66+mkdir -p ~/.config/systemd/user
67+cat > ~/.config/systemd/user/gordon.service <<EOF
68+[Unit]
69+Description=Gordon Container Platform
70+
71+[Service]
72+Type=simple
73+Restart=always
74+ExecStart=/usr/local/bin/gordon start
75+
76+[Install]
77+WantedBy=default.target
78+EOF
79+
80+# Enable and start
81+systemctl --user daemon-reload
82+systemctl --user enable --now gordon
83+sudo loginctl enable-linger $USER
84+```
85+
86+## 6. Deploy Your First App
87+
88+On your local machine:
89+
90+```bash
91+# Build your app
92+docker build -t myapp .
93+
94+# Tag for your registry
95+docker tag myapp registry.mydomain.com/myapp:latest
96+
97+# Push to Gordon
98+docker push registry.mydomain.com/myapp:latest
99+```
100+
101+Your app is now live at `https://app.mydomain.com`!
102+
103+## 7. Update Your App
104+
105+Push a new image to deploy with zero downtime:
106+
107+```bash
108+# Make changes, rebuild
109+docker build -t myapp .
110+docker tag myapp registry.mydomain.com/myapp:latest
111+docker push registry.mydomain.com/myapp:latest
112+```
113+
114+Gordon automatically:
115+1. Starts the new container
116+2. Waits for it to be ready
117+3. Routes traffic to the new container
118+4. Stops the old container
119+
120+## Next Steps
121+
122+- [Installation Guide](./installation.md) - Production setup with firewall and rootless containers
123+- [Configuration Reference](./config/index.md) - All configuration options
124+- [Registry Authentication](./config/registry-auth.md) - Secure your registry
125+- [Environment Variables](./config/env.md) - Configure per-app settings
126+
127+## Related
128+
129+- [First Deploy Tutorial](/wiki/tutorials/first-deploy.md)
130+- [Podman Rootless Setup](/wiki/guides/podman-rootless.md)
A docs/index.md +86 -0
@@ -0,0 +1,86 @@
1+# Gordon Documentation
2+
3+Gordon is a self-hosted container deployment platform that combines a private Docker registry with automatic container deployment.
4+
5+## What is Gordon?
6+
7+Gordon runs on your VPS and provides:
8+
9+- **Private Container Registry** - Push images from your local machine or CI
10+- **HTTP Reverse Proxy** - Routes domains to containers automatically
11+- **Push-to-Deploy** - Containers deploy when you push new images
12+- **Zero-Downtime Updates** - New containers start before old ones stop
13+- **Single Binary** - ~15MB RAM footprint
14+
15+## How It Works
16+
17+```
18+┌─────────────────┐ push ┌─────────────────┐
19+│ Your Machine │ ────────────> │ Gordon Server │
20+│ docker build │ │ │
21+│ docker push │ │ registry:5000 │
22+└─────────────────┘ │ proxy:80 │
23+ └────────┬────────┘
24+ │
25+ v
26+ ┌─────────────────┐
27+ │ Your App Live │
28+ │ app.domain.com │
29+ └─────────────────┘
30+```
31+
32+1. Build your container locally where you have computing power
33+2. Push to your Gordon registry
34+3. Gordon automatically deploys and routes traffic to your container
35+
36+## Quick Navigation
37+
38+### Getting Started
39+
40+- [Getting Started](./getting-started.md) - Deploy your first app in minutes
41+- [Installation](./installation.md) - Detailed installation instructions
42+- [Concepts](./concepts.md) - Core concepts and architecture
43+
44+### Configuration
45+
46+- [Configuration Overview](./config/index.md) - All configuration options
47+- [Server Settings](./config/server.md) - Ports, domains, and runtime
48+- [Routes](./config/routes.md) - Domain to container mapping
49+- [Registry Auth](./config/registry-auth.md) - Password and token authentication
50+- [Secrets](./config/secrets.md) - Secure credential storage
51+- [Network Isolation](./config/network-isolation.md) - Per-app network isolation
52+- [Attachments](./config/attachments.md) - Service dependencies
53+- [Logging](./config/logging.md) - Log collection and rotation
54+- [Environment Variables](./config/env.md) - Per-route environment configuration
55+
56+### CLI Reference
57+
58+- [CLI Overview](./cli/index.md) - Available commands
59+- [gordon start](./cli/start.md) - Start the server
60+- [gordon auth](./cli/auth.md) - Manage authentication
61+
62+### Deployment
63+
64+- [Deployment Overview](./deployment/index.md) - Deployment strategies
65+- [GitHub Actions](./deployment/github-actions.md) - CI/CD with GitHub
66+- [Rollback](./deployment/rollback.md) - Version management and rollback
67+
68+### Reference
69+
70+- [Docker Labels](./reference/docker-labels.md) - Container and image labels
71+- [Environment Variables](./reference/env-variables.md) - Environment variable syntax
72+- [Troubleshooting](./reference/troubleshooting.md) - Common issues and solutions
73+
74+## Requirements
75+
76+- Linux VPS (Ubuntu/Debian recommended)
77+- Docker or Podman runtime
78+- Domain pointing to your server
79+- Cloudflare account for HTTPS (free tier works)
80+
81+> **Note:** Gordon does not handle TLS certificates directly. HTTPS is terminated by Cloudflare (or similar proxy) which forwards HTTP traffic to Gordon internally.
82+
83+## Related
84+
85+- [Wiki Tutorials](/wiki/tutorials/index.md) - Step-by-step guides
86+- [Wiki Examples](/wiki/examples/index.md) - Configuration examples
A docs/installation.md +269 -0
@@ -0,0 +1,269 @@
1+# Installation
2+
3+Detailed installation guide for production environments.
4+
5+## System Requirements
6+
7+- **OS**: Linux (Ubuntu 24.04 LTS, Debian 13 recommended) or macOS
8+- **Architecture**: x86_64 (amd64) or ARM64
9+- **Memory**: 512MB minimum, 1GB recommended
10+- **Disk**: 10GB minimum for registry storage
11+- **Runtime**: Docker or Podman
12+
13+## Download Gordon
14+
15+### From GitHub Releases
16+
17+**Linux (x86_64)**
18+```bash
19+wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
20+tar -xzf gordon_linux_amd64.tar.gz
21+chmod +x gordon
22+sudo mv gordon /usr/local/bin/
23+```
24+
25+**Linux (ARM64)** - for Raspberry Pi 4, AWS Graviton, Oracle Ampere, etc.
26+```bash
27+wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_arm64.tar.gz
28+tar -xzf gordon_linux_arm64.tar.gz
29+chmod +x gordon
30+sudo mv gordon /usr/local/bin/
31+```
32+
33+**macOS (Apple Silicon)**
34+```bash
35+curl -LO https://github.com/bnema/gordon/releases/latest/download/gordon_darwin_arm64.tar.gz
36+tar -xzf gordon_darwin_arm64.tar.gz
37+chmod +x gordon
38+sudo mv gordon /usr/local/bin/
39+```
40+
41+**macOS (Intel)**
42+```bash
43+curl -LO https://github.com/bnema/gordon/releases/latest/download/gordon_darwin_amd64.tar.gz
44+tar -xzf gordon_darwin_amd64.tar.gz
45+chmod +x gordon
46+sudo mv gordon /usr/local/bin/
47+```
48+
49+Verify installation:
50+```bash
51+gordon version
52+```
53+
54+### From Source
55+
56+```bash
57+# Requires Go 1.21+
58+git clone https://github.com/bnema/gordon.git
59+cd gordon
60+make build
61+sudo mv gordon /usr/local/bin/
62+```
63+
64+## Container Runtime Setup
65+
66+### Docker
67+
68+```bash
69+# Install Docker
70+curl -fsSL https://get.docker.com | sh
71+
72+# Add user to docker group
73+sudo usermod -aG docker $USER
74+newgrp docker
75+
76+# Verify
77+docker run hello-world
78+```
79+
80+### Podman (Rootless)
81+
82+Podman rootless mode provides enhanced security by running containers without root privileges.
83+
84+```bash
85+# Install Podman
86+sudo apt update
87+sudo apt install -y podman
88+
89+# Enable user namespaces
90+echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
91+sudo sysctl -p
92+
93+# Setup subuid/subgid
94+sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
95+
96+# Enable Podman socket
97+systemctl --user enable --now podman.socket
98+
99+# Configure insecure localhost registry
100+mkdir -p ~/.config/containers
101+cat > ~/.config/containers/registries.conf <<EOF
102+[registries.insecure]
103+registries = ['localhost:5000']
104+EOF
105+```
106+
107+## Firewall Configuration
108+
109+Gordon needs ports accessible for the registry and proxy.
110+
111+### Using firewalld
112+
113+```bash
114+# Install and enable firewalld
115+sudo apt install -y firewalld
116+sudo systemctl enable --now firewalld
117+
118+# Allow HTTP/HTTPS
119+sudo firewall-cmd --permanent --add-service=http
120+sudo firewall-cmd --permanent --add-service=https
121+
122+# For rootless containers, redirect privileged ports
123+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
124+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
125+
126+# Apply changes
127+sudo firewall-cmd --reload
128+
129+# Verify
130+sudo firewall-cmd --list-all
131+```
132+
133+### Using ufw
134+
135+```bash
136+sudo ufw allow 80/tcp
137+sudo ufw allow 443/tcp
138+sudo ufw allow 5000/tcp # Registry port (if accessing directly)
139+sudo ufw enable
140+```
141+
142+## Configuration
143+
144+Gordon creates a default configuration on first run:
145+
146+```bash
147+# Run once to generate config, then stop
148+gordon start
149+# Press Ctrl+C
150+
151+# Edit configuration
152+nano ~/.config/gordon/gordon.toml
153+```
154+
155+Minimum required configuration:
156+
157+```toml
158+[server]
159+port = 8080
160+registry_port = 5000
161+registry_domain = "registry.yourdomain.com"
162+
163+[routes]
164+"app.yourdomain.com" = "myapp:latest"
165+```
166+
167+See [Configuration Reference](./config/index.md) for all options.
168+
169+## Systemd Service
170+
171+### User Service (Recommended for Rootless)
172+
173+```bash
174+mkdir -p ~/.config/systemd/user
175+cat > ~/.config/systemd/user/gordon.service <<EOF
176+[Unit]
177+Description=Gordon Container Platform
178+After=podman.socket
179+
180+[Service]
181+Type=simple
182+Restart=always
183+RestartSec=5
184+ExecStart=/usr/local/bin/gordon start
185+
186+[Install]
187+WantedBy=default.target
188+EOF
189+
190+# Enable and start
191+systemctl --user daemon-reload
192+systemctl --user enable --now gordon
193+
194+# Enable linger (keep service running after logout)
195+sudo loginctl enable-linger $USER
196+
197+# Check status
198+systemctl --user status gordon
199+```
200+
201+### System Service (Root Mode)
202+
203+```bash
204+sudo cat > /etc/systemd/system/gordon.service <<EOF
205+[Unit]
206+Description=Gordon Container Platform
207+After=docker.service
208+Requires=docker.service
209+
210+[Service]
211+Type=simple
212+Restart=always
213+RestartSec=5
214+ExecStart=/usr/local/bin/gordon start
215+User=root
216+
217+[Install]
218+WantedBy=multi-user.target
219+EOF
220+
221+sudo systemctl daemon-reload
222+sudo systemctl enable --now gordon
223+```
224+
225+## DNS Configuration
226+
227+Point your domains to your server:
228+
229+| Type | Name | Content | Proxy |
230+|------|------|---------|-------|
231+| A | `app` | `SERVER_IP` | Yes (Cloudflare) |
232+| A | `registry` | `SERVER_IP` | Yes (Cloudflare) |
233+
234+> **Note:** Enable Cloudflare proxy (orange cloud) for automatic HTTPS. Gordon receives HTTP traffic from Cloudflare.
235+
236+## Verify Installation
237+
238+```bash
239+# Check Gordon is running
240+systemctl --user status gordon
241+
242+# View logs
243+journalctl --user -u gordon -f
244+
245+# Test registry (from local machine)
246+docker login registry.yourdomain.com
247+docker pull alpine
248+docker tag alpine registry.yourdomain.com/test:latest
249+docker push registry.yourdomain.com/test:latest
250+```
251+
252+## Data Directories
253+
254+Gordon stores data in the following locations:
255+
256+| Path | Purpose |
257+|------|---------|
258+| `~/.config/gordon/gordon.toml` | Configuration file |
259+| `~/.gordon/` | Default data directory |
260+| `~/.gordon/registry/` | Container images |
261+| `~/.gordon/env/` | Environment files |
262+| `~/.gordon/logs/` | Application logs |
263+| `~/.gordon/secrets/` | Secrets (unsafe backend only) |
264+
265+## Related
266+
267+- [Getting Started](./getting-started.md)
268+- [Configuration Reference](./config/index.md)
269+- [Podman Rootless Setup](/wiki/guides/podman-rootless.md)
A docs/reference/docker-labels.md +159 -0
@@ -0,0 +1,159 @@
1+# Docker Labels Reference
2+
3+Labels used by Gordon for container and image metadata.
4+
5+## Container Labels
6+
7+Gordon adds these labels to managed containers:
8+
9+| Label | Value | Description |
10+|-------|-------|-------------|
11+| `gordon.managed` | `"true"` | Identifies Gordon-managed containers |
12+| `gordon.domain` | Domain name | Domain this container serves |
13+| `gordon.image` | Image:tag | Original image from configuration |
14+| `gordon.route` | Domain name | Route this container handles |
15+| `gordon.created` | Timestamp | When Gordon created the container |
16+
17+### Attachment Labels
18+
19+Additional labels for attachment containers:
20+
21+| Label | Value | Description |
22+|-------|-------|-------------|
23+| `gordon.attachment` | `"true"` | Container is an attachment service |
24+| `gordon.attached-to` | Domain/group | Route or network group this serves |
25+
26+## Image Labels
27+
28+Labels you can set in your Dockerfile:
29+
30+| Label | Example | Description |
31+|-------|---------|-------------|
32+| `gordon.proxy.port` | `"3000"` | Port to proxy HTTP traffic to |
33+
34+### Proxy Port Label
35+
36+When an image exposes multiple ports, Gordon needs to know which one serves HTTP:
37+
38+```dockerfile
39+# Gitea exposes SSH (22) and HTTP (3000)
40+FROM gitea/gitea:latest
41+LABEL gordon.proxy.port=3000
42+EXPOSE 22
43+EXPOSE 3000
44+```
45+
46+Without this label, Gordon uses the first exposed port.
47+
48+**When to use:**
49+- Image exposes multiple ports
50+- First exposed port isn't the HTTP service
51+- You want explicit control over routing
52+
53+## Container Naming
54+
55+Gordon names containers:
56+
57+| Pattern | Example |
58+|---------|---------|
59+| `gordon-{domain}` | `gordon-app-mydomain-com` |
60+| `gordon-{domain}-new` | `gordon-app-mydomain-com-new` (during updates) |
61+| `gordon-{domain}-{service}` | `gordon-app-mydomain-com-postgres` (attachments) |
62+
63+## Inspecting Labels
64+
65+View labels on a container:
66+
67+```bash
68+docker inspect gordon-app-mydomain-com --format '{{json .Config.Labels}}' | jq
69+```
70+
71+Example output:
72+
73+```json
74+{
75+ "gordon.created": "2024-01-15T10:30:00Z",
76+ "gordon.domain": "app.mydomain.com",
77+ "gordon.image": "myapp:latest",
78+ "gordon.managed": "true",
79+ "gordon.route": "app.mydomain.com"
80+}
81+```
82+
83+## Filtering Containers
84+
85+Find Gordon-managed containers:
86+
87+```bash
88+# All Gordon containers
89+docker ps -f "label=gordon.managed=true"
90+
91+# Containers for specific domain
92+docker ps -f "label=gordon.domain=app.mydomain.com"
93+
94+# Attachment containers
95+docker ps -f "label=gordon.attachment=true"
96+
97+# Attachments for specific route
98+docker ps -f "label=gordon.attached-to=app.mydomain.com"
99+```
100+
101+## Examples
102+
103+### Standard Application Container
104+
105+```bash
106+docker inspect gordon-app-mydomain-com --format '{{json .Config.Labels}}'
107+```
108+
109+```json
110+{
111+ "gordon.managed": "true",
112+ "gordon.domain": "app.mydomain.com",
113+ "gordon.image": "myapp:v2.1.0",
114+ "gordon.route": "app.mydomain.com",
115+ "gordon.created": "2024-01-15T10:30:00Z"
116+}
117+```
118+
119+### Attachment Container
120+
121+```bash
122+docker inspect gordon-app-mydomain-com-postgres --format '{{json .Config.Labels}}'
123+```
124+
125+```json
126+{
127+ "gordon.managed": "true",
128+ "gordon.attachment": "true",
129+ "gordon.attached-to": "app.mydomain.com",
130+ "gordon.created": "2024-01-15T10:29:00Z"
131+}
132+```
133+
134+### Multi-Port Dockerfile
135+
136+```dockerfile
137+FROM node:18
138+LABEL gordon.proxy.port=3000
139+
140+WORKDIR /app
141+COPY . .
142+
143+# HTTP server on 3000
144+EXPOSE 3000
145+# Metrics on 9090
146+EXPOSE 9090
147+# WebSocket on 8080
148+EXPOSE 8080
149+
150+CMD ["npm", "start"]
151+```
152+
153+Gordon routes HTTP to port 3000.
154+
155+## Related
156+
157+- [Configuration Overview](../config/index.md)
158+- [Attachments](../config/attachments.md)
159+- [Concepts](../concepts.md)
A docs/reference/env-variables.md +222 -0
@@ -0,0 +1,222 @@
1+# Environment Variables Reference
2+
3+How Gordon handles environment variables for containers.
4+
5+## Variable Sources
6+
7+Environment variables are loaded from multiple sources and merged:
8+
9+| Source | Priority | Description |
10+|--------|----------|-------------|
11+| Dockerfile `ENV` | Lowest | Default values from image |
12+| `.env` file | Highest | Per-route overrides |
13+
14+Higher priority sources override lower priority values.
15+
16+## Environment File Location
17+
18+Files are stored in the env directory (default: `~/.gordon/env/`):
19+
20+```
21+~/.gordon/env/
22+├── app_mydomain_com.env
23+├── api_mydomain_com.env
24+└── admin_mydomain_com.env
25+```
26+
27+**Naming:** Replace dots with underscores, add `.env` suffix.
28+
29+| Domain | File Name |
30+|--------|-----------|
31+| `app.mydomain.com` | `app_mydomain_com.env` |
32+| `api.company.io` | `api_company_io.env` |
33+| `staging.app.dev` | `staging_app_dev.env` |
34+
35+## File Format
36+
37+Standard `.env` format:
38+
39+```bash
40+# Comments start with #
41+KEY=value
42+ANOTHER_KEY=another value
43+
44+# Quotes are optional but recommended for values with spaces
45+MESSAGE="Hello World"
46+
47+# No spaces around =
48+DATABASE_URL=postgresql://localhost:5432/mydb
49+```
50+
51+## Secret Provider Syntax
52+
53+Reference secrets from configured backends:
54+
55+### Pass Provider
56+
57+```bash
58+# Syntax: ${pass:<path>}
59+DATABASE_PASSWORD=${pass:myapp/database/password}
60+API_KEY=${pass:company/api-key}
61+JWT_SECRET=${pass:production/jwt-secret}
62+```
63+
64+### SOPS Provider
65+
66+```bash
67+# Syntax: ${sops:<file>:<key.path>}
68+DATABASE_PASSWORD=${sops:secrets.yaml:database.password}
69+API_SECRET=${sops:production.yaml:api.secret.key}
70+STRIPE_KEY=${sops:secrets.yaml:stripe.api_key}
71+```
72+
73+## Variable Expansion
74+
75+### From Secrets
76+
77+```bash
78+# Password from pass
79+DB_PASSWORD=${pass:myapp/db-password}
80+
81+# Then use in connection string
82+DATABASE_URL=postgresql://user:${DB_PASSWORD}@db:5432/mydb
83+```
84+
85+### Static Values
86+
87+```bash
88+# Simple values
89+NODE_ENV=production
90+PORT=3000
91+
92+# Values with special characters (use quotes)
93+MESSAGE="Hello, World!"
94+JSON_CONFIG='{"key": "value"}'
95+```
96+
97+## Dockerfile ENV
98+
99+Variables from Dockerfile serve as defaults:
100+
101+```dockerfile
102+FROM node:18
103+ENV NODE_ENV=development
104+ENV PORT=3000
105+ENV LOG_LEVEL=info
106+```
107+
108+Override in `.env` file:
109+
110+```bash
111+# Override NODE_ENV, keep PORT and LOG_LEVEL defaults
112+NODE_ENV=production
113+```
114+
115+Result:
116+- `NODE_ENV=production` (from .env)
117+- `PORT=3000` (from Dockerfile)
118+- `LOG_LEVEL=info` (from Dockerfile)
119+
120+## Common Variables
121+
122+### Node.js
123+
124+```bash
125+NODE_ENV=production
126+PORT=3000
127+LOG_LEVEL=warn
128+```
129+
130+### Python
131+
132+```bash
133+FLASK_ENV=production
134+DJANGO_SETTINGS_MODULE=myapp.settings.production
135+PYTHONUNBUFFERED=1
136+```
137+
138+### Database Connections
139+
140+```bash
141+# PostgreSQL
142+DATABASE_URL=postgresql://user:pass@postgres:5432/mydb
143+
144+# MySQL
145+DATABASE_URL=mysql://user:pass@mysql:3306/mydb
146+
147+# Redis
148+REDIS_URL=redis://redis:6379/0
149+```
150+
151+### External Services
152+
153+```bash
154+# AWS
155+AWS_ACCESS_KEY_ID=${pass:aws/access-key}
156+AWS_SECRET_ACCESS_KEY=${pass:aws/secret-key}
157+AWS_REGION=us-east-1
158+
159+# Stripe
160+STRIPE_SECRET_KEY=${pass:stripe/secret-key}
161+STRIPE_PUBLISHABLE_KEY=pk_live_...
162+
163+# SendGrid
164+SENDGRID_API_KEY=${pass:sendgrid/api-key}
165+```
166+
167+## Examples
168+
169+### Minimal
170+
171+```bash
172+NODE_ENV=production
173+PORT=3000
174+```
175+
176+### With Database
177+
178+```bash
179+NODE_ENV=production
180+PORT=3000
181+DATABASE_URL=postgresql://postgres:5432/myapp
182+DATABASE_PASSWORD=${pass:myapp/db-password}
183+```
184+
185+### Full Production
186+
187+```bash
188+# Application
189+NODE_ENV=production
190+PORT=3000
191+LOG_LEVEL=warn
192+
193+# Database
194+DATABASE_URL=postgresql://postgres:5432/production
195+DATABASE_PASSWORD=${pass:company/db-password}
196+
197+# Cache
198+REDIS_URL=redis://redis:6379/0
199+
200+# External APIs
201+STRIPE_SECRET_KEY=${pass:company/stripe-secret}
202+SENDGRID_API_KEY=${pass:company/sendgrid-key}
203+
204+# Auth
205+JWT_SECRET=${pass:company/jwt-secret}
206+SESSION_SECRET=${pass:company/session-secret}
207+
208+# Feature flags
209+ENABLE_ANALYTICS=true
210+MAINTENANCE_MODE=false
211+```
212+
213+## Viewing Container Environment
214+
215+```bash
216+docker inspect gordon-app-mydomain-com --format '{{json .Config.Env}}' | jq
217+```
218+
219+## Related
220+
221+- [Environment Configuration](../config/env.md)
222+- [Secrets Configuration](../config/secrets.md)
A docs/reference/troubleshooting.md +337 -0
@@ -0,0 +1,337 @@
1+# Troubleshooting
2+
3+Common issues and solutions when using Gordon.
4+
5+## Registry Issues
6+
7+### "unauthorized: authentication required"
8+
9+**Cause:** Registry authentication is enabled but credentials are missing or invalid.
10+
11+**Solutions:**
12+
13+1. Login to registry:
14+ ```bash
15+ docker login registry.mydomain.com
16+ ```
17+
18+2. Check token is valid:
19+ ```bash
20+ gordon auth token list
21+ ```
22+
23+3. Generate new token:
24+ ```bash
25+ gordon auth token generate --subject myuser --expiry 0
26+ ```
27+
28+### "connection refused" on push
29+
30+**Cause:** Gordon is not running or registry port is blocked.
31+
32+**Solutions:**
33+
34+1. Check Gordon is running:
35+ ```bash
36+ systemctl --user status gordon
37+ # or
38+ pgrep -f "gordon start"
39+ ```
40+
41+2. Check registry port is accessible:
42+ ```bash
43+ curl -v http://localhost:5000/v2/
44+ ```
45+
46+3. Check firewall:
47+ ```bash
48+ sudo firewall-cmd --list-ports
49+ ```
50+
51+### "unknown: image not found"
52+
53+**Cause:** Image was pushed but no route configured.
54+
55+**Solution:** Add route to config:
56+```toml
57+[routes]
58+"app.mydomain.com" = "myapp:latest"
59+```
60+
61+Then reload:
62+```bash
63+gordon reload
64+```
65+
66+## Deployment Issues
67+
68+### Container starts but app not accessible
69+
70+**Causes and solutions:**
71+
72+1. **Wrong port exposed:**
73+ Check your Dockerfile exposes the correct port:
74+ ```dockerfile
75+ EXPOSE 3000
76+ ```
77+
78+2. **Multiple ports - wrong one selected:**
79+ Add `gordon.proxy.port` label:
80+ ```dockerfile
81+ LABEL gordon.proxy.port=3000
82+ ```
83+
84+3. **App not listening on 0.0.0.0:**
85+ Ensure app binds to `0.0.0.0`, not `127.0.0.1`:
86+ ```javascript
87+ app.listen(3000, '0.0.0.0');
88+ ```
89+
90+### Container keeps restarting
91+
92+**Cause:** Application crashing on startup.
93+
94+**Solutions:**
95+
96+1. Check container logs:
97+ ```bash
98+ docker logs gordon-app-mydomain-com
99+ ```
100+
101+2. Check Gordon logs:
102+ ```bash
103+ gordon logs -f
104+ ```
105+
106+3. Run container manually to debug:
107+ ```bash
108+ docker run -it registry.mydomain.com/myapp:latest sh
109+ ```
110+
111+### Environment variables not loaded
112+
113+**Cause:** Env file not found or wrong format.
114+
115+**Solutions:**
116+
117+1. Check file exists with correct name:
118+ ```bash
119+ ls ~/.gordon/env/
120+ # Should show: app_mydomain_com.env (dots → underscores)
121+ ```
122+
123+2. Check file permissions:
124+ ```bash
125+ chmod 600 ~/.gordon/env/app_mydomain_com.env
126+ ```
127+
128+3. Check file format (no spaces around `=`):
129+ ```bash
130+ # Correct
131+ KEY=value
132+
133+ # Wrong
134+ KEY = value
135+ ```
136+
137+### Secrets not resolved
138+
139+**Cause:** Secret provider not available or secret not found.
140+
141+**Solutions:**
142+
143+1. For `pass` backend:
144+ ```bash
145+ # Check pass is available
146+ which pass
147+
148+ # Check secret exists
149+ pass show myapp/db-password
150+ ```
151+
152+2. For `sops` backend:
153+ ```bash
154+ # Check sops is available
155+ which sops
156+
157+ # Check file can be decrypted
158+ sops -d secrets.yaml
159+ ```
160+
161+## Network Issues
162+
163+### Containers can't reach each other
164+
165+**Cause:** Network isolation enabled but services not in same network.
166+
167+**Solutions:**
168+
169+1. Check attachments are configured:
170+ ```toml
171+ [attachments]
172+ "app.mydomain.com" = ["postgres:latest"]
173+ ```
174+
175+2. Check containers are in same network:
176+ ```bash
177+ docker network inspect gordon-app-mydomain-com
178+ ```
179+
180+3. Use correct hostname (image name before colon):
181+ ```javascript
182+ // Correct
183+ connect("postgresql://postgres:5432/mydb")
184+
185+ // Wrong
186+ connect("postgresql://localhost:5432/mydb")
187+ ```
188+
189+### DNS resolution failing
190+
191+**Cause:** Container can't resolve service names.
192+
193+**Solutions:**
194+
195+1. Verify network isolation is enabled:
196+ ```toml
197+ [network_isolation]
198+ enabled = true
199+ ```
200+
201+2. Check both containers in same network:
202+ ```bash
203+ docker network inspect gordon-app-mydomain-com
204+ ```
205+
206+## Volume Issues
207+
208+### Data not persisting
209+
210+**Cause:** Volume not configured or not preserved.
211+
212+**Solutions:**
213+
214+1. Add VOLUME to Dockerfile:
215+ ```dockerfile
216+ VOLUME ["/data"]
217+ ```
218+
219+2. Check volume preservation:
220+ ```toml
221+ [volumes]
222+ preserve = true # default: true
223+ ```
224+
225+3. List volumes:
226+ ```bash
227+ docker volume ls | grep gordon
228+ ```
229+
230+### Disk full
231+
232+**Cause:** Registry or logs consuming too much space.
233+
234+**Solutions:**
235+
236+1. Check disk usage:
237+ ```bash
238+ du -sh ~/.gordon/*
239+ ```
240+
241+2. Prune unused images:
242+ ```bash
243+ docker image prune -a
244+ ```
245+
246+3. Configure log rotation:
247+ ```toml
248+ [logging.file]
249+ max_size = 100
250+ max_backups = 3
251+ ```
252+
253+## Configuration Issues
254+
255+### Config not reloading
256+
257+**Cause:** Gordon not watching config file.
258+
259+**Solution:** Manual reload:
260+```bash
261+gordon reload
262+```
263+
264+### "failed to read config file"
265+
266+**Cause:** TOML syntax error.
267+
268+**Solution:** Validate TOML:
269+```bash
270+# Check for syntax errors
271+cat ~/.config/gordon/gordon.toml | tomlv
272+# or use online validator
273+```
274+
275+## Logging Issues
276+
277+### No logs appearing
278+
279+**Cause:** File logging not enabled.
280+
281+**Solution:** Enable in config:
282+```toml
283+[logging.file]
284+enabled = true
285+path = "~/.gordon/logs/gordon.log"
286+```
287+
288+### Container logs missing
289+
290+**Cause:** Container log collection disabled.
291+
292+**Solution:**
293+```toml
294+[logging.container_logs]
295+enabled = true # default: true
296+```
297+
298+## Diagnostic Commands
299+
300+```bash
301+# Check Gordon status
302+systemctl --user status gordon
303+
304+# View Gordon logs
305+gordon logs -f
306+journalctl --user -u gordon -f
307+
308+# List containers
309+docker ps -f "label=gordon.managed=true"
310+
311+# List networks
312+docker network ls | grep gordon
313+
314+# List volumes
315+docker volume ls | grep gordon
316+
317+# Check container logs
318+docker logs gordon-app-mydomain-com
319+
320+# Inspect container
321+docker inspect gordon-app-mydomain-com
322+
323+# Check connectivity
324+curl -v http://localhost:5000/v2/
325+curl -v http://localhost:8080/
326+```
327+
328+## Getting Help
329+
330+- [GitHub Issues](https://github.com/bnema/gordon/issues)
331+- [GitHub Discussions](https://github.com/bnema/gordon/discussions)
332+
333+## Related
334+
335+- [Installation](../installation.md)
336+- [Configuration](../config/index.md)
337+- [CLI Reference](../cli/index.md)
D examples/README.md +0 -29
@@ -1,29 +0,0 @@
1-# Examples
2-
3-Config is auto-generated on first `gordon start`. Use these examples as reference or replace the generated config:
4-
5-```bash
6-cp examples/production.toml ~/.config/gordon/gordon.toml
7-```
8-
9-## Configurations
10-
11-| File | Use case |
12-|------|----------|
13-| [`minimal.toml`](minimal.toml) | Single app, no auth. Start here. |
14-| [`development.toml`](development.toml) | Local .local domains, unsafe secrets |
15-| [`staging.toml`](staging.toml) | Branch previews, token auth |
16-| [`production.toml`](production.toml) | Pinned versions, full logging |
17-| [`saas-multi-tenant.toml`](saas-multi-tenant.toml) | Customer subdomains, shared services |
18-| [`logging.toml`](logging.toml) | All logging options explained |
19-
20-## Workflows
21-
22-| File | Purpose |
23-|------|---------|
24-| [`github-workflow.yml`](github-workflow.yml) | Deploy on git tag push |
25-| [`rollback-workflow-example.md`](rollback-workflow-example.md) | Version management with manifests |
26-
27-## Config Reference
28-
29-See comments in each `.toml` file. Full docs: [README](../README.md#configuration)
D examples/development.toml +0 -166
@@ -1,166 +0,0 @@
1-# Gordon v2 - Development Configuration Example
2-# For local development and testing
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "registry.yourdomain.com" # Your Gordon registry domain
8-runtime = "auto" # auto-detects Docker/Podman
9-ssl_email = "dev@localhost"
10-data_dir = "./dev-data"
11-
12-# Secrets backend for development
13-[secrets]
14-backend = "unsafe" # Plain text storage for local development
15-
16-[registry_auth]
17-enabled = false # Disable auth for easier local development
18-
19-# To enable auth for development:
20-# enabled = true
21-# type = "password"
22-# username = "dev"
23-# password_hash = "gordon/registry/password_hash" # Generate: gordon auth password hash
24-
25-[auto_route]
26-enabled = true # Enable automatic route creation from image names
27-
28-# Volume configuration - auto-creates persistent storage from VOLUME directives
29-[volumes]
30-auto_create = true # Default: true (automatically handle VOLUME directives)
31-prefix = "gordon" # Default: "gordon" (volume name prefix)
32-preserve = true # Default: true (keep volumes when containers are removed)
33-
34-# Logging configuration for development
35-[logging]
36-enabled = true # Enable file-based logging
37-level = "debug" # Verbose logging for development
38-dir = "./dev-data/logs" # Store logs in dev data directory
39-main_log_file = "gordon.log" # Main application logs
40-proxy_log_file = "proxy.log" # HTTP proxy traffic logs
41-container_log_dir = "containers" # Container logs subdirectory
42-
43-# Log rotation settings - smaller for development
44-max_size = 10 # Max 10MB per log file
45-max_backups = 3 # Keep 3 old files
46-max_age = 7 # Keep logs for 1 week
47-compress = true # Compress old logs
48-
49-# Environment variables configuration
50-[env]
51-# Directory where .env files are stored for each route
52-dir = "./dev-data/env"
53-# Note: Secret providers use [secrets].backend configuration
54-
55-# Development routes using your domain
56-[routes]
57-"app.yourdomain.com" = "myapp:latest" # Main app - latest build
58-"api.yourdomain.com" = "myapi:latest" # API service
59-"admin.yourdomain.com" = "admin:latest" # Admin interface
60-"docs.yourdomain.com" = "docs:latest" # Documentation
61-
62-# Test different versions
63-"app-v1.yourdomain.com" = "myapp:v1.0.0" # Test older version
64-"app-v2.yourdomain.com" = "myapp:v2.0.0" # Test newer version
65-
66-# Third-party services for development
67-"db-admin.yourdomain.com" = "phpmyadmin:latest" # Database admin
68-"mail.yourdomain.com" = "mailhog/mailhog:latest" # Email testing
69-
70-# Network isolation configuration
71-[network_isolation]
72-enabled = true # Enable per-app network isolation
73-network_prefix = "gordon" # Default prefix for network names
74-dns_suffix = ".internal" # DNS suffix for internal services
75-
76-# Service attachments - each app gets its own isolated network with attached services
77-[attachments]
78-"app.yourdomain.com" = ["my-postgres:latest", "my-redis:latest"] # App with database and cache
79-"api.yourdomain.com" = ["my-postgres:latest"] # API with its own database
80-"admin.yourdomain.com" = ["my-mysql:latest"] # Admin with different database
81-
82-# Optional: Network groups for shared services between apps
83-# [network_groups]
84-# "backend" = ["app.yourdomain.com", "api.yourdomain.com"] # Share services between app and api
85-#
86-# [attachments]
87-# "backend" = ["my-redis:latest", "rabbitmq:latest"] # Shared cache and queue
88-# "app.yourdomain.com" = ["my-postgres:latest"] # App-specific database
89-# "api.yourdomain.com" = ["my-postgres:latest"] # API-specific database
90-# "admin.yourdomain.com" = ["my-mysql:latest"] # Admin gets its own isolated network
91-
92-# Environment Variables Usage:
93-#
94-# For development, create .env files for your routes:
95-# ./dev-data/env/app_yourdomain_com.env
96-# ./dev-data/env/api_yourdomain_com.env
97-#
98-# Example env file content:
99-# NODE_ENV=development
100-# DEBUG=true
101-# DATABASE_URL=postgresql://localhost:5432/dev
102-# API_KEY=${pass:dev/api-key} # Optional: use pass for dev secrets
103-
104-# Development workflow:
105-# 1. Point DNS to your Gordon server:
106-# A *.yourdomain.com → YOUR_GORDON_SERVER_IP
107-# A yourdomain.com → YOUR_GORDON_SERVER_IP
108-#
109-# 2. Build your app: docker build -t myapp:latest .
110-# 3. Create env file: ./dev-data/env/app_yourdomain_com.env
111-# 4. Push to Gordon registry:
112-# docker tag myapp:latest registry.yourdomain.com/myapp:latest
113-# docker push registry.yourdomain.com/myapp:latest
114-# 5. Access at https://app.yourdomain.com
115-#
116-# AUTO-ROUTE FEATURE:
117-# With auto_route.enabled = true, you can also push images with domain names as image names:
118-# docker build -t myapp .
119-# docker tag myapp:latest registry.yourdomain.com/mytestapp.bamen.dev:latest
120-# docker push registry.yourdomain.com/mytestapp.bamen.dev:latest
121-# Gordon will automatically create a route for mytestapp.bamen.dev -> mytestapp.bamen.dev:latest
122-#
123-# VOLUME FEATURE:
124-# Add VOLUME directives to your Dockerfile for automatic persistent storage:
125-# VOLUME ["/app/data", "/app/uploads"]
126-# Gordon automatically creates and mounts persistent volumes - no config needed!
127-#
128-# ENV DIRECTIVE FEATURE:
129-# Add ENV directives to your Dockerfile for default environment variables:
130-# ENV NODE_ENV=production
131-# ENV PORT=3000
132-# ENV LOG_LEVEL=info
133-# Gordon automatically reads these and merges them with your .env files!
134-# Your .env files always override Dockerfile ENV directives.
135-#
136-# Example Dockerfile with both features:
137-# FROM node:18-alpine
138-# WORKDIR /app
139-# ENV NODE_ENV=production
140-# ENV PORT=3000
141-# VOLUME ["/app/data", "/app/logs"]
142-# COPY . .
143-# EXPOSE 3000
144-# CMD ["npm", "start"]
145-#
146-# Cloudflare handles SSL and DNS for your remote Gordon server!
147-#
148-# NETWORK ISOLATION FEATURE:
149-# With attachments, each app gets its own isolated network with attached services.
150-# Services are accessible by their simple names within the network:
151-# - postgresql://postgres:5432/mydb
152-# - redis://redis:6379
153-# - mysql://mysql:3306/mydb
154-#
155-# To use persistent storage with attachments, create custom Dockerfiles:
156-#
157-# Example my-postgres Dockerfile:
158-# FROM postgres:15
159-# VOLUME ["/var/lib/postgresql/data"]
160-# ENV POSTGRES_DB=myapp
161-# ENV POSTGRES_USER=appuser
162-# ENV POSTGRES_PASSWORD=secret
163-#
164-# Build and push to registry:
165-# docker build -f Dockerfile.postgres -t registry.yourdomain.com/my-postgres:latest .
166-# docker push registry.yourdomain.com/my-postgres:latest⏎̸
D examples/docker-compose.yml +0 -108
@@ -1,108 +0,0 @@
1-# Gordon v2 - Docker Compose Deployment Example
2-# Complete containerized deployment with proper networking
3-
4-version: '3.8'
5-
6-services:
7- # Main Gordon service
8- gordon:
9- image: gordon:latest
10- container_name: gordon
11- restart: unless-stopped
12-
13- # Port mapping for Cloudflare → Gordon
14- ports:
15- - "80:8080" # HTTP traffic (Cloudflare terminates SSL)
16- - "443:8080" # HTTPS traffic (Cloudflare → HTTP to Gordon)
17-
18- volumes:
19- # Docker socket access for container management
20- - /var/run/docker.sock:/var/run/docker.sock
21-
22- # Data persistence
23- - ./gordon-data:/data
24-
25- # Configuration
26- - ./gordon.toml:/app/gordon.toml:ro
27-
28- # Optional: Mount Docker registry data
29- - ./registry-data:/data/registry
30-
31- environment:
32- - GORDON_LOG_LEVEL=info
33- - GORDON_CONFIG=/app/gordon.toml
34-
35- # Health check
36- healthcheck:
37- test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
38- interval: 30s
39- timeout: 10s
40- retries: 3
41- start_period: 40s
42-
43- networks:
44- - gordon-network
45-
46- # Security
47- user: "1000:1000" # Run as non-root user
48-
49- # Resource limits (optional)
50- deploy:
51- resources:
52- limits:
53- memory: 512M
54- cpus: '0.5'
55- reservations:
56- memory: 256M
57- cpus: '0.25'
58-
59- # Optional: Monitoring with Prometheus
60- prometheus:
61- image: prom/prometheus:latest
62- container_name: gordon-prometheus
63- restart: unless-stopped
64- ports:
65- - "9090:9090"
66- volumes:
67- - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
68- - prometheus-data:/prometheus
69- command:
70- - '--config.file=/etc/prometheus/prometheus.yml'
71- - '--storage.tsdb.path=/prometheus'
72- - '--web.console.libraries=/etc/prometheus/console_libraries'
73- - '--web.console.templates=/etc/prometheus/consoles'
74- - '--web.enable-lifecycle'
75- networks:
76- - gordon-network
77-
78- # Optional: Grafana dashboard
79- grafana:
80- image: grafana/grafana:latest
81- container_name: gordon-grafana
82- restart: unless-stopped
83- ports:
84- - "3000:3000"
85- volumes:
86- - grafana-data:/var/lib/grafana
87- environment:
88- - GF_SECURITY_ADMIN_PASSWORD=admin123
89- networks:
90- - gordon-network
91-
92-networks:
93- gordon-network:
94- driver: bridge
95- ipam:
96- config:
97- - subnet: 172.20.0.0/16
98-
99-volumes:
100- prometheus-data:
101- grafana-data:
102-
103-# Usage:
104-# 1. Create gordon.toml with your configuration
105-# 2. Run: docker-compose up -d
106-# 3. Check health: docker-compose ps
107-# 4. View logs: docker-compose logs -f gordon
108-# 5. Scale if needed: docker-compose up -d --scale gordon=2⏎̸
D examples/github-workflow.yml +0 -129
@@ -1,129 +0,0 @@
1-# Example GitHub Actions workflow for deploying to Gordon
2-# Copy this file to your repository as .github/workflows/deploy.yml
3-#
4-# Prerequisites:
5-# 1. Generate a Gordon token on your server:
6-# gordon auth token generate --subject github-actions --scopes push,pull --expiry 0
7-#
8-# 2. Add these secrets to your GitHub repository:
9-# - GORDON_REGISTRY: Your registry URL (e.g., registry.mydomain.com)
10-# - GORDON_USERNAME: Token subject (e.g., github-actions)
11-# - GORDON_TOKEN: The generated JWT token
12-
13-name: Deploy to Gordon
14-
15-on:
16- # Deploy on version tags
17- push:
18- tags:
19- - 'v*' # Matches v1.0.0, v2.1.3, etc.
20- - 'release-*' # Matches release-2024-01-15, etc.
21-
22- # Optional: Allow manual deployments
23- workflow_dispatch:
24- inputs:
25- tag:
26- description: 'Tag/version to deploy (leave empty for latest commit)'
27- required: false
28- type: string
29-
30-jobs:
31- deploy:
32- runs-on: ubuntu-latest
33-
34- # Optional: Only deploy from main branch for manual triggers
35- # if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
36-
37- steps:
38- - name: Checkout code
39- uses: actions/checkout@v6
40- with:
41- # For manual dispatch with specific tag
42- ref: ${{ github.event.inputs.tag || github.ref }}
43-
44- - name: Deploy to Gordon
45- id: deploy
46- uses: bnema/gordon/.github/actions/deploy@main
47- with:
48- registry: ${{ secrets.GORDON_REGISTRY }}
49- username: ${{ secrets.GORDON_USERNAME }}
50- password: ${{ secrets.GORDON_TOKEN }}
51- tag: ${{ github.event.inputs.tag }}
52- # Optional customizations:
53- # image: myapp # Custom image name (defaults to repo name)
54- # dockerfile: ./Dockerfile # Custom Dockerfile path
55- # context: . # Build context
56- # push-latest: 'true' # Also push :latest tag
57- # platforms: linux/amd64 # Target platform(s)
58-
59- - name: Deployment summary
60- run: |
61- echo "## Deployment Complete" >> $GITHUB_STEP_SUMMARY
62- echo "" >> $GITHUB_STEP_SUMMARY
63- echo "**Image:** \`${{ steps.deploy.outputs.image }}\`" >> $GITHUB_STEP_SUMMARY
64- echo "**Tag:** \`${{ steps.deploy.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
65- echo "" >> $GITHUB_STEP_SUMMARY
66- echo "Gordon will automatically deploy this image to your configured routes." >> $GITHUB_STEP_SUMMARY
67-
68-# ============================================================================
69-# ALTERNATIVE CONFIGURATIONS
70-# ============================================================================
71-
72-# --- Deploy on push to main (continuous deployment) ---
73-#
74-# on:
75-# push:
76-# branches:
77-# - main
78-#
79-# Then in the action, the image will be tagged with the commit SHA.
80-
81-# --- Deploy multiple services (monorepo) ---
82-#
83-# jobs:
84-# deploy-api:
85-# runs-on: ubuntu-latest
86-# steps:
87-# - uses: actions/checkout@v6
88-# - uses: bnema/gordon/.github/actions/deploy@main
89-# with:
90-# registry: ${{ secrets.GORDON_REGISTRY }}
91-# username: ${{ secrets.GORDON_USERNAME }}
92-# password: ${{ secrets.GORDON_TOKEN }}
93-# image: myapp-api
94-# dockerfile: ./services/api/Dockerfile
95-# context: ./services/api
96-#
97-# deploy-web:
98-# runs-on: ubuntu-latest
99-# steps:
100-# - uses: actions/checkout@v6
101-# - uses: bnema/gordon/.github/actions/deploy@main
102-# with:
103-# registry: ${{ secrets.GORDON_REGISTRY }}
104-# username: ${{ secrets.GORDON_USERNAME }}
105-# password: ${{ secrets.GORDON_TOKEN }}
106-# image: myapp-web
107-# dockerfile: ./services/web/Dockerfile
108-# context: ./services/web
109-
110-# --- Deploy with build arguments ---
111-#
112-# - uses: bnema/gordon/.github/actions/deploy@main
113-# with:
114-# registry: ${{ secrets.GORDON_REGISTRY }}
115-# username: ${{ secrets.GORDON_USERNAME }}
116-# password: ${{ secrets.GORDON_TOKEN }}
117-# build-args: |
118-# NODE_ENV=production
119-# API_URL=https://api.example.com
120-# BUILD_DATE=${{ github.event.head_commit.timestamp }}
121-
122-# --- Multi-platform builds ---
123-#
124-# - uses: bnema/gordon/.github/actions/deploy@main
125-# with:
126-# registry: ${{ secrets.GORDON_REGISTRY }}
127-# username: ${{ secrets.GORDON_USERNAME }}
128-# password: ${{ secrets.GORDON_TOKEN }}
129-# platforms: linux/amd64,linux/arm64
D examples/logging.toml +0 -185
@@ -1,185 +0,0 @@
1-# Gordon v2 - Comprehensive Logging Configuration Example
2-# Demonstrates all logging features and configurations
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "registry.yourdomain.com"
8-runtime = "auto"
9-data_dir = "./data"
10-
11-# Secrets backend
12-[secrets]
13-backend = "pass" # "pass", "sops", or "unsafe"
14-
15-[registry_auth]
16-enabled = true
17-type = "password" # or "token"
18-username = "admin"
19-password_hash = "gordon/registry/password_hash" # bcrypt hash in secrets backend
20-
21-# ============================================================================
22-# LOGGING CONFIGURATION EXAMPLES
23-# ============================================================================
24-
25-# Basic logging setup - good for most users
26-[logging]
27-enabled = true # Enable file-based logging (default: false)
28-level = "info" # Log level: trace, debug, info, warn, error (default: info)
29-dir = "./logs" # Directory for log files (default: ./logs)
30-main_log_file = "gordon.log" # Main application log file (default: gordon.log)
31-proxy_log_file = "proxy.log" # HTTP proxy traffic log file (default: proxy.log)
32-container_log_dir = "containers" # Container logs subdirectory (default: containers)
33-
34-# Log rotation settings
35-max_size = 100 # Max log file size in MB before rotation (default: 100)
36-max_backups = 5 # Number of old log files to keep (default: 5)
37-max_age = 30 # Max age in days to keep old log files (default: 30)
38-compress = true # Compress old log files with gzip (default: true)
39-
40-# ============================================================================
41-# ALTERNATIVE LOGGING CONFIGURATIONS
42-# ============================================================================
43-
44-# Development logging configuration (uncomment to use)
45-# [logging]
46-# enabled = true
47-# level = "debug" # More verbose for debugging
48-# dir = "./dev-logs"
49-# main_log_file = "gordon-dev.log"
50-# proxy_log_file = "proxy-dev.log"
51-# container_log_dir = "containers"
52-# max_size = 10 # Smaller files for development
53-# max_backups = 3 # Less history needed
54-# max_age = 7 # Keep logs for 1 week
55-# compress = false # No compression for faster access
56-
57-# Production logging configuration (uncomment to use)
58-# [logging]
59-# enabled = true
60-# level = "warn" # Only warnings and errors in production
61-# dir = "/var/log/gordon" # System log directory
62-# main_log_file = "gordon.log"
63-# proxy_log_file = "proxy.log"
64-# container_log_dir = "containers"
65-# max_size = 200 # Larger files for production
66-# max_backups = 15 # More history for analysis
67-# max_age = 90 # Keep logs for 3 months
68-# compress = true # Save disk space
69-
70-# High-traffic logging configuration (uncomment to use)
71-# [logging]
72-# enabled = true
73-# level = "error" # Only errors to reduce log volume
74-# dir = "/var/log/gordon"
75-# main_log_file = "gordon.log"
76-# proxy_log_file = "proxy.log"
77-# container_log_dir = "containers"
78-# max_size = 500 # Very large files for high traffic
79-# max_backups = 20 # Extensive history
80-# max_age = 180 # Keep logs for 6 months
81-# compress = true # Essential for large files
82-
83-# Minimal logging configuration (uncomment to use)
84-# [logging]
85-# enabled = true
86-# level = "error" # Only log errors
87-# dir = "./logs"
88-# max_size = 50 # Small files
89-# max_backups = 2 # Minimal history
90-# max_age = 14 # 2 weeks retention
91-# compress = true
92-
93-# Disable logging (console output only) - uncomment to use
94-# [logging]
95-# enabled = false # Only console output, no files
96-
97-# ============================================================================
98-# LOG STRUCTURE CREATED BY GORDON
99-# ============================================================================
100-#
101-# When logging is enabled, Gordon creates this structure:
102-#
103-# ./logs/ # Main log directory (permissions: 0700)
104-# ├── gordon.log # Main application logs (permissions: 0600)
105-# ├── gordon.log.1.gz # Rotated logs (compressed)
106-# ├── gordon.log.2.gz
107-# ├── proxy.log # HTTP proxy traffic logs (permissions: 0600)
108-# ├── proxy.log.1.gz # Rotated proxy logs
109-# └── containers/ # Container logs directory (permissions: 0700)
110-# ├── abc123def456.log # Container logs by ID (permissions: 0600)
111-# ├── def456ghi789.log
112-# ├── myapp_yourdomain_com.log # Symlink to container ID for easier access
113-# └── api_yourdomain_com.log # Symlink to container ID for easier access
114-#
115-# ============================================================================
116-# WHAT GETS LOGGED
117-# ============================================================================
118-#
119-# Main Application Logs (gordon.log):
120-# - Server startup and shutdown events
121-# - Container lifecycle events (deploy, start, stop, remove)
122-# - Configuration changes and hot reloads
123-# - Registry operations (push/pull events)
124-# - Error conditions and warnings
125-# - Authentication events
126-# - Auto-route creation events
127-#
128-# Proxy Logs (proxy.log):
129-# - All HTTP/HTTPS requests through the reverse proxy
130-# - Request method, URL, response code, response time
131-# - Client IP addresses and user agents
132-# - SSL/TLS connection information
133-# - Upstream container routing decisions
134-#
135-# Container Logs (containers/*.log):
136-# - Real-time stdout/stderr from each container
137-# - Timestamped application output
138-# - Container startup and shutdown messages
139-# - Application errors and debug output
140-# - Custom application logging
141-#
142-# ============================================================================
143-# SECURITY FEATURES
144-# ============================================================================
145-#
146-# - Log directories created with 0700 permissions (owner-only access)
147-# - Log files created with 0600 permissions (owner read/write only)
148-# - Automatic log rotation prevents disk space exhaustion
149-# - Sensitive information (passwords, tokens) is automatically redacted
150-# - Container logs are isolated per container for security analysis
151-#
152-# ============================================================================
153-# LOG MONITORING COMMANDS
154-# ============================================================================
155-#
156-# Real-time monitoring:
157-# tail -f logs/gordon.log # Watch main application logs
158-# tail -f logs/proxy.log # Monitor HTTP traffic
159-# tail -f logs/containers/*.log # Follow all container logs
160-#
161-# Log analysis:
162-# grep -i error logs/gordon.log # Find errors in main logs
163-# grep "deploy" logs/gordon.log # Find deployment events
164-# awk '{print $9}' logs/proxy.log | sort | uniq -c # HTTP status code analysis
165-# grep "container" logs/gordon.log | tail -20 # Recent container events
166-#
167-# ============================================================================
168-
169-[routes]
170-"app.yourdomain.com" = "myapp:latest"
171-"api.yourdomain.com" = "myapi:latest"
172-"logs.yourdomain.com" = "logviewer:latest" # Example log viewing service
173-
174-# Example environment variables configuration
175-[env]
176-dir = "./data/env"
177-providers = ["pass"]
178-
179-# With comprehensive logging enabled, you can:
180-# 1. Monitor all HTTP traffic to your applications
181-# 2. Debug container deployment issues
182-# 3. Analyze application performance
183-# 4. Track security events and access patterns
184-# 5. Maintain audit trails for compliance
185-# 6. Troubleshoot network and routing issues⏎̸
D examples/minimal.toml +0 -62
@@ -1,62 +0,0 @@
1-# Gordon v2 - Minimal Configuration Example
2-# Perfect for getting started
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "registry.local"
8-
9-# Secrets backend (optional, defaults to "pass")
10-# [secrets]
11-# backend = "unsafe" # Use "unsafe" for local dev without pass/sops
12-
13-[registry_auth]
14-enabled = false # Disabled for local development
15-
16-# To enable auth, choose a type:
17-# enabled = true
18-# type = "password" # Simple username/password
19-# username = "dev"
20-# password_hash = "gordon/registry/password_hash" # Generate with: gordon auth password hash
21-#
22-# OR for token auth (better for CI):
23-# type = "token"
24-# token_secret = "gordon/registry/token_secret"
25-# Generate tokens with: gordon auth token generate --subject dev --expiry 0
26-
27-# Optional: Enable basic logging
28-# [logging]
29-# enabled = true
30-# level = "info"
31-# dir = "./logs"
32-
33-# Environment variables (optional)
34-[env]
35-dir = "./data/env" # Default location
36-
37-# Simple routes
38-[routes]
39-"app.local" = "myapp:latest"
40-"api.local" = "myapi:latest"
41-
42-# Usage:
43-# 1. Create env files (optional):
44-# ./data/env/app_local.env
45-# ./data/env/api_local.env
46-#
47-# 2. Example env file content:
48-# NODE_ENV=production
49-# PORT=3000
50-# DATABASE_URL=postgresql://localhost:5432/app
51-#
52-# 3. Add to /etc/hosts:
53-# 127.0.0.1 app.local
54-# 127.0.0.1 api.local
55-# 127.0.0.1 registry.local
56-#
57-# 4. Build and push:
58-# docker build -t myapp:latest .
59-# docker tag myapp:latest registry.local/myapp:latest
60-# docker push registry.local/myapp:latest
61-#
62-# 5. Access at http://app.local:8080⏎̸
D examples/production.toml +0 -132
@@ -1,132 +0,0 @@
1-# Gordon v2 - Production Configuration Example
2-# Optimized for production environments with multiple services
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "registry.company.com"
8-runtime = "auto" # auto-detects Docker/Podman
9-# socket_path = "" # optional: override with custom socket path
10-ssl_email = "devops@company.com"
11-data_dir = "/var/lib/gordon"
12-
13-# Secrets backend for sensitive data
14-[secrets]
15-backend = "pass" # "pass" (recommended), "sops", or "unsafe"
16-
17-[registry_auth]
18-enabled = true
19-type = "token" # "token" (recommended for CI/CD) or "password"
20-
21-# Token auth: JWT-based, ideal for CI pipelines
22-token_secret = "gordon/registry/token_secret" # path in secrets backend
23-
24-# Generate tokens with:
25-# gordon auth token generate --subject deploy-bot --scopes push,pull --expiry 0
26-# Each Gordon instance has unique tokens (different token_secret = incompatible tokens)
27-
28-# Alternative: Password auth (simpler but less flexible)
29-# type = "password"
30-# username = "deploy-bot"
31-# password_hash = "gordon/registry/password_hash" # bcrypt hash in secrets backend
32-# Generate hash with: gordon auth password hash
33-
34-# Production logging configuration
35-[logging]
36-enabled = true # Enable comprehensive logging
37-level = "info" # Standard production logging level
38-dir = "/var/log/gordon" # System log directory
39-main_log_file = "gordon.log" # Main application logs
40-proxy_log_file = "proxy.log" # HTTP proxy traffic logs
41-container_log_dir = "containers" # Container logs subdirectory
42-
43-# Production log rotation settings
44-max_size = 100 # Max 100MB per log file
45-max_backups = 10 # Keep 10 old files for history
46-max_age = 90 # Keep logs for 3 months
47-compress = true # Compress old logs to save space
48-
49-# Environment variables configuration
50-[env]
51-# Directory where .env files are stored for each route
52-dir = "/var/lib/gordon/env"
53-# Note: Secret providers now use [secrets].backend configuration
54-
55-# Production service routes
56-[routes]
57-"app.company.com" = "company-app:v2.1.0" # Main app with pinned version
58-"api.company.com" = "company-api:v1.5.2" # API service
59-"admin.company.com" = "admin-panel:v1.0.1" # Admin interface
60-"docs.company.com" = "company-docs:latest" # Documentation site
61-"status.company.com" = "status-page:v0.3.0" # Status page
62-
63-# Monitoring and internal tools
64-"grafana.company.com" = "grafana/grafana:latest" # Metrics dashboard
65-"prometheus.company.com" = "prom/prometheus:latest" # Metrics collection
66-
67-# Network isolation for production security
68-[network_isolation]
69-enabled = true # Enable per-app network isolation
70-network_prefix = "prod" # Production network prefix
71-dns_suffix = ".internal" # Internal DNS suffix
72-
73-# Network groups for microservices that need to communicate
74-[network_groups]
75-"backend" = ["app.company.com", "api.company.com"] # App and API share services
76-"monitoring" = ["grafana.company.com", "prometheus.company.com"] # Monitoring stack
77-
78-# Service attachments with isolated networks
79-[attachments]
80-# Shared backend services
81-"backend" = ["company-redis:latest", "company-rabbitmq:latest"]
82-# Individual databases for isolation
83-"app.company.com" = ["company-postgres:latest"]
84-"api.company.com" = ["company-postgres:latest"]
85-"admin.company.com" = ["company-mysql:latest"]
86-# Monitoring services get their own network
87-"monitoring" = ["company-prometheus-data:latest"]
88-
89-# Environment Variables Usage:
90-#
91-# For each route, create a .env file named after the domain:
92-# /var/lib/gordon/env/app_company_com.env
93-# /var/lib/gordon/env/api_company_com.env
94-#
95-# Example env file content:
96-# NODE_ENV=production
97-# DATABASE_URL=postgresql://user:pass@db:5432/prod
98-# API_KEY=${pass:company/api-key} # From pass
99-# SECRET_KEY=${sops:secrets.yaml:app.secret} # From sops
100-#
101-# Secret syntax:
102-# ${pass:path/to/secret} - Unix password manager
103-# ${sops:file.yaml:key.path} - SOPS encrypted file
104-
105-# Production deployment workflow:
106-# 1. Build and tag: docker build -t company-app:v2.1.1 .
107-# 2. Tag for registry: docker tag company-app:v2.1.1 registry.company.com/company-app:v2.1.1
108-# 3. Update this config: "app.company.com" = "company-app:v2.1.1"
109-# 4. Create/update env file: /var/lib/gordon/env/app_company_com.env
110-# 5. Restart Gordon to reload config
111-# 6. Push to deploy: docker push registry.company.com/company-app:v2.1.1
112-# 7. Gordon automatically deploys to app.company.com with environment variables!
113-#
114-# NETWORK ISOLATION IN PRODUCTION:
115-# - Each app gets its own isolated network (prod-app-company-com, prod-admin-company-com)
116-# - Network groups allow controlled sharing (prod-backend, prod-monitoring)
117-# - Services communicate via simple DNS names (redis:6379, postgres:5432)
118-# - Complete isolation between tenant/service boundaries
119-#
120-# Example service Dockerfiles for persistent storage:
121-#
122-# company-postgres/Dockerfile:
123-# FROM postgres:15
124-# VOLUME ["/var/lib/postgresql/data"]
125-# ENV POSTGRES_DB=production
126-# ENV POSTGRES_USER=app
127-# ENV POSTGRES_PASSWORD=supersecret
128-#
129-# company-redis/Dockerfile:
130-# FROM redis:7-alpine
131-# VOLUME ["/data"]
132-# CMD ["redis-server", "--appendonly", "yes"]⏎̸
D examples/rollback-workflow-example.md +0 -192
@@ -1,192 +0,0 @@
1-# Gordon Version Deployment Workflow Example
2-
3-This document demonstrates how to use manifest annotations for simple version deployments and rollbacks in Gordon.
4-
5-## Prerequisites
6-
7-- Gordon server running with the registry and auto-route features enabled
8-- Podman or Docker with OCI manifest support
9-- A container image to deploy
10-
11-## Basic Versioned Deployment
12-
13-### 1. Build and Push Your Versioned Images
14-
15-```bash
16-# Build and push your application images with versions
17-export VERSION=v1.0.1
18-podman build --tag myapp:$VERSION --tag registry.domain.com/myapp:$VERSION .
19-podman push registry.domain.com/myapp:$VERSION
20-
21-export VERSION=v1.0.2
22-podman build --tag myapp:$VERSION --tag registry.domain.com/myapp:$VERSION .
23-podman push registry.domain.com/myapp:$VERSION
24-```
25-
26-### 2. Create Manifest and Deploy
27-
28-```bash
29-# Deploy v1.0.1 with version annotation
30-export VERSION=v1.0.1
31-podman manifest create myapp:latest
32-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
33-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
34-podman manifest push myapp:latest registry.domain.com/myapp:latest
35-```
36-
37-### 3. Gordon Processing
38-
39-When Gordon receives the manifest, it will:
40-
41-1. Parse the manifest annotations from the JSON structure:
42- ```json
43- {
44- "schemaVersion": 2,
45- "mediaType": "application/vnd.oci.image.manifest.v1+json",
46- "annotations": {
47- "version": "v1.0.1"
48- }
49- }
50- ```
51-
52-2. Extract version information and deploy the specified version
53-3. Update routes to use the versioned image
54-
55-## Simple Version Deployment and Rollback
56-
57-### Deploying Different Versions
58-
59-```bash
60-# Deploy v1.0.2
61-export VERSION=v1.0.2
62-podman manifest create myapp:latest --amend
63-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
64-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
65-podman manifest push myapp:latest registry.domain.com/myapp:latest
66-
67-# Rollback to v1.0.1 (just change the version)
68-export VERSION=v1.0.1
69-podman manifest create myapp:latest --amend
70-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
71-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
72-podman manifest push myapp:latest registry.domain.com/myapp:latest
73-
74-# Deploy v1.0.3 (assuming you built and pushed it first)
75-export VERSION=v1.0.3
76-podman manifest create myapp:latest --amend
77-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
78-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
79-podman manifest push myapp:latest registry.domain.com/myapp:latest
80-```
81-
82-### Gordon Version Processing
83-
84-When Gordon detects version annotations, it will:
85-
86-1. **Parse version annotations**: Extract the version from the `version` annotation
87-2. **Execute deployment**: Deploy the specified version to all matching routes
88-3. **Log the deployment**
89-
90-**Example log output**:
91-```
92-INFO Processing versioned deployment image=myapp reference=latest version=v1.0.1
93-INFO Performing version deployment route=myapp.domain.com versioned_image=myapp:v1.0.1
94-INFO Version deployment completed successfully route=myapp.domain.com version=v1.0.1
95-```
96-
97-## Supported Annotation Keys
98-
99-### Version Annotations
100-- `version` - The version to deploy (required)
101-
102-## Real-World Example
103-
104-```bash
105-# Deploy production v1.0.1
106-export VERSION=v1.0.1
107-podman manifest create myapp:latest --amend
108-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
109-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
110-podman manifest push myapp:latest registry.domain.com/myapp:latest
111-
112-# Deploy new version v1.0.2
113-export VERSION=v1.0.2
114-podman manifest create myapp:latest --amend
115-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
116-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
117-podman manifest push myapp:latest registry.domain.com/myapp:latest
118-
119-# Oh no! Bug found - rollback to v1.0.1
120-export VERSION=v1.0.1
121-podman manifest create myapp:latest --amend
122-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
123-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
124-podman manifest push myapp:latest registry.domain.com/myapp:latest
125-
126-# Fix deployed - move forward to v1.0.3
127-export VERSION=v1.0.3
128-podman manifest create myapp:latest --amend
129-podman manifest add myapp:latest registry.domain.com/myapp:$VERSION
130-podman manifest annotate myapp:latest --annotation version=$VERSION registry.domain.com/myapp:$VERSION
131-podman manifest push myapp:latest registry.domain.com/myapp:latest
132-```
133-
134-## Configuration Requirements
135-
136-Ensure your Gordon configuration supports this workflow:
137-
138-```toml
139-[auto_route]
140-enabled = true
141-
142-[registry_auth]
143-enabled = true
144-username = "gordon"
145-password = "your-password"
146-
147-[server]
148-registry_domain = "registry.domain.com"
149-```
150-
151-## Monitoring and Observability
152-
153-Gordon logs provide visibility into the version deployment process:
154-
155-- Manifest annotation parsing
156-- Version detection and tracking
157-- Deployment execution
158-- Success/failure status
159-
160-Check Gordon logs for detailed version deployment information:
161-
162-```bash
163-# View Gordon logs for version operations
164-journalctl -u gordon -f | grep -i version
165-
166-# Or check Docker/Podman logs if running in container
167-docker logs gordon-container | grep -i version
168-```
169-
170-## Troubleshooting
171-
172-### Common Issues
173-
174-1. **Annotations not parsed**: Ensure you're using OCI manifest format, not Docker v2.2
175-2. **Version target not found**: Verify the target version exists in your registry
176-3. **Route not found**: Ensure the image has configured routes in Gordon
177-4. **Permission errors**: Check registry authentication and permissions
178-
179-### Debugging Commands
180-
181-```bash
182-# Inspect manifest annotations
183-podman manifest inspect myapp:latest
184-
185-# Check Gordon configuration
186-gordon config show
187-
188-# Verify image exists
189-podman search registry.domain.com/myapp
190-```
191-
192-This version deployment workflow provides a simple, declarative way to manage deployments and rollbacks using standard container registry operations and OCI manifest annotations.⏎̸
D examples/saas-multi-tenant.toml +0 -61
@@ -1,61 +0,0 @@
1-# Gordon v2 - Multi-Tenant SaaS Configuration Example
2-# For SaaS platforms with customer subdomains
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "registry.saas-platform.com"
8-runtime = "auto" # auto-detects Docker/Podman
9-ssl_email = "ops@saas-platform.com"
10-data_dir = "/var/lib/gordon"
11-
12-# Secrets backend for secure credential storage
13-[secrets]
14-backend = "pass" # Production should use "pass" or "sops"
15-
16-[registry_auth]
17-enabled = true
18-type = "token" # Token auth for automated deployments
19-
20-# JWT signing secret (unique to this Gordon instance)
21-token_secret = "gordon/saas/token_secret"
22-
23-# Generate deployment tokens:
24-# gordon auth token generate --subject deploy-bot --scopes push,pull --expiry 0
25-# Each Gordon instance has unique tokens (prevents cross-instance token reuse)
26-
27-# Multi-tenant customer routes
28-[routes]
29-# Main platform services
30-"app.saas-platform.com" = "saas-frontend:v2.1.0" # Marketing site
31-"admin.saas-platform.com" = "saas-admin:v1.5.0" # Admin dashboard
32-"api.saas-platform.com" = "saas-api:v3.2.1" # Main API
33-
34-# Customer tenant subdomains
35-"acme-corp.saas-platform.com" = "saas-app:v2.1.0" # Customer: Acme Corp
36-"beta-inc.saas-platform.com" = "saas-app:v2.1.0" # Customer: Beta Inc
37-"gamma-llc.saas-platform.com" = "saas-app:v2.1.0" # Customer: Gamma LLC
38-
39-# Enterprise customers with custom domains
40-"portal.acme-corp.com" = "saas-app:v2.1.0" # Acme's custom domain
41-"app.beta-inc.com" = "saas-app:v2.1.0" # Beta's custom domain
42-
43-# Staging/testing tenants
44-"demo.saas-platform.com" = "saas-app:latest" # Demo environment
45-"staging-tenant.saas-platform.com" = "saas-app:staging" # Staging tests
46-
47-# Support tools
48-"status.saas-platform.com" = "status-page:v1.0.0" # Status page
49-"docs.saas-platform.com" = "documentation:v2.0.0" # Customer docs
50-
51-# Multi-tenant deployment strategy:
52-# 1. Single application image serves all tenants
53-# 2. Tenant identification via subdomain/domain
54-# 3. Application handles tenant routing internally
55-# 4. Each tenant gets isolated data/configuration
56-# 5. Rolling updates deploy to all tenants simultaneously
57-#
58-# Scaling strategy:
59-# - Start with shared containers (same image, tenant routing)
60-# - Move to dedicated containers per tenant if needed
61-# - Use Gordon's event system for tenant provisioning automation⏎̸
D examples/staging.toml +0 -70
@@ -1,70 +0,0 @@
1-# Gordon v2 - Staging Configuration Example
2-# For testing and preview environments
3-
4-[server]
5-port = 8080
6-registry_port = 5000
7-registry_domain = "staging-registry.company.com"
8-runtime = "auto" # auto-detects Docker/Podman
9-ssl_email = "devops@company.com"
10-data_dir = "./data"
11-
12-# Secrets backend for staging
13-[secrets]
14-backend = "pass" # Use pass for secure secret storage
15-
16-[registry_auth]
17-enabled = true
18-type = "token" # Token auth for CI/CD pipelines
19-
20-# JWT signing secret stored in pass
21-token_secret = "gordon/staging/token_secret"
22-
23-# Generate CI tokens with:
24-# gordon auth token generate --subject ci-staging --scopes push,pull --expiry 0
25-# Tokens are unique to this Gordon instance (different token_secret = incompatible tokens)
26-
27-# Alternative: Password auth
28-# type = "password"
29-# username = "staging-user"
30-# password_hash = "gordon/staging/password_hash" # bcrypt hash in pass
31-
32-# Staging logging configuration
33-[logging]
34-enabled = true # Enable logging for staging debugging
35-level = "debug" # Verbose logging for troubleshooting
36-dir = "./staging-logs" # Staging-specific log directory
37-main_log_file = "gordon.log" # Main application logs
38-proxy_log_file = "proxy.log" # HTTP proxy traffic logs
39-container_log_dir = "containers" # Container logs subdirectory
40-
41-# Staging log rotation settings
42-max_size = 50 # Moderate file sizes for staging
43-max_backups = 5 # Keep reasonable history
44-max_age = 30 # Keep logs for 1 month
45-compress = true # Compress to save space
46-
47-# Staging environment routes - using branch-based deployments
48-[routes]
49-"staging.company.com" = "company-app:staging" # Main staging branch
50-"staging-api.company.com" = "company-api:staging" # API staging
51-
52-# Feature branch testing
53-"feature-auth.company.com" = "company-app:feature-auth" # Feature branch: auth improvements
54-"feature-ui.company.com" = "company-app:feature-ui-redesign" # Feature branch: UI updates
55-"hotfix-123.company.com" = "company-app:hotfix-user-login" # Hotfix branch
56-
57-# PR preview environments
58-"pr-42.company.com" = "company-app:pr-42" # Pull request #42 preview
59-"pr-55.company.com" = "company-app:pr-55" # Pull request #55 preview
60-
61-# Development tools
62-"staging-admin.company.com" = "admin-panel:develop" # Admin panel dev version
63-
64-# Staging workflow:
65-# 1. Developer pushes feature branch
66-# 2. CI builds and tags: company-app:feature-auth
67-# 3. CI pushes: docker push staging-registry.company.com/company-app:feature-auth
68-# 4. Gordon auto-deploys to feature-auth.company.com
69-# 5. Team tests at https://feature-auth.company.com
70-# 6. Merge to staging → auto-deploy to staging.company.com⏎̸
A wiki/examples/index.md +68 -0
@@ -0,0 +1,68 @@
1+# Configuration Examples
2+
3+Annotated Gordon configuration examples for common scenarios.
4+
5+## Examples
6+
7+- [Minimal Configuration](./minimal.md) - Simplest working setup
8+- [Production Configuration](./production.md) - Full production setup
9+- [Development Configuration](./development.md) - Local development setup
10+
11+## Quick Reference
12+
13+### Minimal
14+
15+```toml
16+[server]
17+registry_domain = "registry.local"
18+
19+[routes]
20+"app.local" = "myapp:latest"
21+```
22+
23+### Development
24+
25+```toml
26+[server]
27+port = 8080
28+registry_domain = "registry.local"
29+
30+[registry_auth]
31+enabled = false
32+
33+[auto_route]
34+enabled = true
35+
36+[routes]
37+"app.local" = "myapp:latest"
38+```
39+
40+### Production
41+
42+```toml
43+[server]
44+port = 8080
45+registry_domain = "registry.company.com"
46+
47+[secrets]
48+backend = "pass"
49+
50+[registry_auth]
51+enabled = true
52+type = "token"
53+token_secret = "gordon/registry/token_secret"
54+
55+[network_isolation]
56+enabled = true
57+
58+[routes]
59+"app.company.com" = "company-app:v2.1.0"
60+
61+[attachments]
62+"app.company.com" = ["postgres:latest", "redis:latest"]
63+```
64+
65+## Related
66+
67+- [Configuration Reference](/docs/config/index.md)
68+- [Installation Guide](/docs/installation.md)
A wiki/examples/minimal.md +97 -0
@@ -0,0 +1,97 @@
1+# Minimal Configuration
2+
3+The simplest working Gordon configuration.
4+
5+## When to Use
6+
7+- Getting started with Gordon
8+- Local development
9+- Testing Gordon features
10+
11+## Configuration
12+
13+```toml
14+# ~/.config/gordon/gordon.toml
15+
16+[server]
17+port = 8080
18+registry_port = 5000
19+registry_domain = "registry.local"
20+
21+# Authentication disabled for simplicity
22+[registry_auth]
23+enabled = false
24+
25+# Simple route
26+[routes]
27+"app.local" = "myapp:latest"
28+```
29+
30+## Setup
31+
32+### 1. Add to /etc/hosts
33+
34+```bash
35+echo "127.0.0.1 app.local registry.local" | sudo tee -a /etc/hosts
36+```
37+
38+### 2. Start Gordon
39+
40+```bash
41+gordon start
42+```
43+
44+### 3. Build and Deploy
45+
46+```bash
47+docker build -t myapp .
48+docker tag myapp registry.local:5000/myapp:latest
49+docker push registry.local:5000/myapp:latest
50+```
51+
52+### 4. Access
53+
54+Open http://app.local:8080
55+
56+## What's Included
57+
58+| Feature | Enabled |
59+|---------|---------|
60+| Registry | Yes |
61+| HTTP Proxy | Yes |
62+| Authentication | No |
63+| Logging | Console only |
64+| Network Isolation | No |
65+| Attachments | No |
66+
67+## What's Not Included
68+
69+- HTTPS (requires Cloudflare or similar)
70+- Registry authentication
71+- File-based logging
72+- Network isolation
73+- Service attachments
74+
75+## Next Steps
76+
77+To add more features:
78+
79+```toml
80+# Enable logging
81+[logging.file]
82+enabled = true
83+path = "~/.gordon/logs/gordon.log"
84+
85+# Enable network isolation
86+[network_isolation]
87+enabled = true
88+
89+# Add attachments
90+[attachments]
91+"app.local" = ["postgres:latest"]
92+```
93+
94+## Related
95+
96+- [Development Configuration](./development.md)
97+- [Production Configuration](./production.md)
A wiki/examples/production.md +181 -0
@@ -0,0 +1,181 @@
1+# Production Configuration
2+
3+A complete Gordon configuration for production environments.
4+
5+## When to Use
6+
7+- Production deployments
8+- Multiple applications
9+- Services requiring databases
10+
11+## Configuration
12+
13+```toml
14+# ~/.config/gordon/gordon.toml
15+
16+# Server settings
17+[server]
18+port = 8080 # Cloudflare forwards 443 → 8080
19+registry_port = 5000
20+registry_domain = "registry.company.com"
21+
22+# Use pass for secrets (recommended)
23+[secrets]
24+backend = "pass"
25+
26+# Token authentication for CI/CD
27+[registry_auth]
28+enabled = true
29+type = "token"
30+token_secret = "gordon/registry/token_secret"
31+
32+# File-based logging with rotation
33+[logging]
34+level = "info"
35+format = "json"
36+
37+[logging.file]
38+enabled = true
39+path = "~/.gordon/logs/gordon.log"
40+max_size = 100
41+max_backups = 10
42+max_age = 90
43+
44+[logging.container_logs]
45+enabled = true # enabled by default
46+dir = "~/.gordon/logs/containers"
47+max_size = 100
48+max_backups = 10
49+max_age = 90
50+
51+# Environment directory
52+[env]
53+dir = "~/.gordon/env"
54+
55+# Volume settings (all enabled by default)
56+[volumes]
57+auto_create = true
58+prefix = "gordon"
59+preserve = true
60+
61+# Network isolation for security
62+[network_isolation]
63+enabled = true
64+network_prefix = "prod"
65+dns_suffix = ".internal"
66+
67+# Application routes with pinned versions
68+[routes]
69+"app.company.com" = "company-app:v2.1.0"
70+"api.company.com" = "company-api:v1.5.2"
71+"admin.company.com" = "admin-panel:v1.0.1"
72+"docs.company.com" = "company-docs:latest"
73+
74+# Network groups for shared services
75+[network_groups]
76+"backend" = ["app.company.com", "api.company.com"]
77+
78+# Service attachments
79+[attachments]
80+"backend" = ["company-redis:latest"]
81+"app.company.com" = ["company-postgres:latest"]
82+"api.company.com" = ["company-postgres:latest"]
83+```
84+
85+## Setup Steps
86+
87+### 1. Install Pass
88+
89+```bash
90+sudo apt install pass gnupg
91+gpg --gen-key
92+pass init your-gpg-key-id
93+```
94+
95+### 2. Store Token Secret
96+
97+```bash
98+# Generate random secret
99+openssl rand -base64 32 | pass insert -m gordon/registry/token_secret
100+```
101+
102+### 3. Generate CI Token
103+
104+```bash
105+gordon auth token generate --subject ci-bot --scopes push,pull --expiry 0
106+```
107+
108+### 4. Create Environment Files
109+
110+```bash
111+# App environment
112+cat > ~/.gordon/env/app_company_com.env <<EOF
113+NODE_ENV=production
114+PORT=3000
115+DATABASE_URL=postgresql://company-postgres:5432/app
116+DATABASE_PASSWORD=\${pass:company/db-password}
117+REDIS_URL=redis://company-redis:6379
118+EOF
119+
120+# API environment
121+cat > ~/.gordon/env/api_company_com.env <<EOF
122+NODE_ENV=production
123+PORT=8080
124+DATABASE_URL=postgresql://company-postgres:5432/api
125+DATABASE_PASSWORD=\${pass:company/db-password}
126+REDIS_URL=redis://company-redis:6379
127+EOF
128+```
129+
130+### 5. Configure Cloudflare
131+
132+| Type | Name | Content | Proxy |
133+|------|------|---------|-------|
134+| A | `app` | VPS IP | Yes |
135+| A | `api` | VPS IP | Yes |
136+| A | `admin` | VPS IP | Yes |
137+| A | `registry` | VPS IP | Yes |
138+
139+### 6. Start Gordon
140+
141+```bash
142+systemctl --user enable --now gordon
143+```
144+
145+## Features Enabled
146+
147+| Feature | Status |
148+|---------|--------|
149+| Registry | Enabled |
150+| Token Auth | Enabled |
151+| File Logging | Enabled with rotation |
152+| Container Logs | Enabled with rotation |
153+| Network Isolation | Enabled |
154+| Attachments | Configured |
155+| Secrets (pass) | Enabled |
156+
157+## Deployment Workflow
158+
159+```bash
160+# Build locally
161+docker build -t company-app .
162+
163+# Tag with version
164+docker tag company-app registry.company.com/company-app:v2.2.0
165+
166+# Push to deploy
167+docker push registry.company.com/company-app:v2.2.0
168+
169+# Update config with new version
170+vim ~/.config/gordon/gordon.toml
171+# Change: "app.company.com" = "company-app:v2.2.0"
172+
173+# Reload to deploy
174+gordon reload
175+```
176+
177+## Related
178+
179+- [Minimal Configuration](./minimal.md)
180+- [Secrets Configuration](/docs/config/secrets.md)
181+- [Network Isolation](/docs/config/network-isolation.md)
A wiki/guides/index.md +24 -0
@@ -0,0 +1,24 @@
1+# Guides
2+
3+In-depth guides for specific Gordon setups and integrations.
4+
5+## Server Setup
6+
7+- [Podman Rootless Setup](./podman-rootless.md) - Enhanced security with rootless containers
8+
9+## External Services
10+
11+- [Cloudflare DNS & Proxy Setup](./cloudflare-setup.md) - HTTPS termination with Cloudflare
12+
13+## Secrets Management
14+
15+- [Using Pass for Secrets](./secrets-pass.md) - Unix password manager integration
16+- [Using SOPS for Secrets](./secrets-sops.md) - Encrypted secrets with SOPS
17+
18+## Prerequisites
19+
20+These guides assume you have:
21+
22+1. Basic Linux administration experience
23+2. Gordon installed (see [Installation](/docs/installation.md))
24+3. Domain name available for configuration
A wiki/guides/podman-rootless.md +234 -0
@@ -0,0 +1,234 @@
1+# Podman Rootless Setup
2+
3+Set up Gordon with Podman in rootless mode for enhanced security.
4+
5+## What You'll Learn
6+
7+- Installing Podman for rootless containers
8+- Configuring firewall port forwarding
9+- Running Gordon as a user service
10+- Troubleshooting common issues
11+
12+## Prerequisites
13+
14+- Ubuntu 24.04 LTS or Debian 13
15+- VPS with root access (for initial setup)
16+- Domain pointing to your VPS
17+
18+## Why Rootless?
19+
20+Rootless containers run without root privileges, providing:
21+
22+- **Security**: Container escapes don't grant root access
23+- **Isolation**: Each user has separate container namespace
24+- **Simplicity**: No Docker daemon required
25+
26+## Steps
27+
28+### 1. System Preparation
29+
30+```bash
31+# Update system
32+sudo apt update && sudo apt upgrade -y
33+
34+# Install Podman and firewalld
35+sudo apt install -y podman firewalld
36+
37+# Enable firewalld
38+sudo systemctl enable --now firewalld
39+```
40+
41+### 2. Configure User Namespaces
42+
43+```bash
44+# Enable user namespaces
45+echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
46+sudo sysctl -p
47+
48+# Setup subuid/subgid for your user
49+sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
50+```
51+
52+### 3. Configure Firewall Port Forwarding
53+
54+Rootless containers can't bind to ports below 1024. Forward 80/443 to high ports:
55+
56+```bash
57+# Allow HTTP/HTTPS traffic
58+sudo firewall-cmd --permanent --add-service=http
59+sudo firewall-cmd --permanent --add-service=https
60+
61+# Forward privileged ports to unprivileged
62+sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
63+sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443
64+
65+# Apply changes
66+sudo firewall-cmd --reload
67+
68+# Verify
69+sudo firewall-cmd --list-all
70+```
71+
72+### 4. Enable Podman Socket
73+
74+```bash
75+# Enable user Podman socket
76+systemctl --user enable --now podman.socket
77+
78+# Verify
79+systemctl --user status podman.socket
80+```
81+
82+### 5. Configure Registry Access
83+
84+Allow pushing to localhost registry without TLS:
85+
86+```bash
87+mkdir -p ~/.config/containers
88+cat > ~/.config/containers/registries.conf <<EOF
89+[registries.insecure]
90+registries = ['localhost:5000']
91+EOF
92+```
93+
94+### 6. Install Gordon
95+
96+```bash
97+# Download (choose your architecture)
98+wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_amd64.tar.gz
99+# or for ARM64:
100+# wget https://github.com/bnema/gordon/releases/latest/download/gordon_linux_arm64.tar.gz
101+
102+tar -xzf gordon_linux_*.tar.gz
103+chmod +x gordon
104+sudo mv gordon /usr/local/bin/
105+```
106+
107+### 7. Configure Gordon
108+
109+Generate initial config:
110+
111+```bash
112+gordon start
113+# Press Ctrl+C after config is created
114+```
115+
116+Edit `~/.config/gordon/gordon.toml`:
117+
118+```toml
119+[server]
120+port = 8080 # Must match firewall forward (80 → 8080)
121+registry_port = 5000
122+registry_domain = "registry.mydomain.com"
123+
124+[routes]
125+"app.mydomain.com" = "myapp:latest"
126+```
127+
128+### 8. Create Systemd User Service
129+
130+```bash
131+mkdir -p ~/.config/systemd/user
132+cat > ~/.config/systemd/user/gordon.service <<EOF
133+[Unit]
134+Description=Gordon Container Platform
135+After=podman.socket
136+
137+[Service]
138+Type=simple
139+Restart=always
140+RestartSec=5
141+ExecStart=/usr/local/bin/gordon start
142+
143+[Install]
144+WantedBy=default.target
145+EOF
146+
147+# Enable and start
148+systemctl --user daemon-reload
149+systemctl --user enable --now gordon
150+```
151+
152+### 9. Enable Linger
153+
154+Keep services running after logout:
155+
156+```bash
157+sudo loginctl enable-linger $USER
158+```
159+
160+### 10. Verify Setup
161+
162+```bash
163+# Check Gordon status
164+systemctl --user status gordon
165+
166+# Check logs
167+journalctl --user -u gordon -f
168+
169+# Test registry
170+curl -v http://localhost:5000/v2/
171+```
172+
173+## Tailscale Integration
174+
175+If using Tailscale for management:
176+
177+```bash
178+# Add Tailscale interface to trusted zone
179+sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0
180+
181+# Allow Tailscale WireGuard port
182+sudo firewall-cmd --permanent --add-port=41641/udp
183+
184+# Apply
185+sudo firewall-cmd --reload
186+```
187+
188+## Common Issues
189+
190+### "permission denied" on podman
191+
192+```bash
193+# Ensure podman socket is running
194+systemctl --user status podman.socket
195+
196+# Restart if needed
197+systemctl --user restart podman.socket
198+```
199+
200+### Containers can't access network
201+
202+```bash
203+# Check slirp4netns is installed
204+which slirp4netns
205+
206+# Install if missing
207+sudo apt install slirp4netns
208+```
209+
210+### Port forwarding not working
211+
212+```bash
213+# Verify rules
214+sudo firewall-cmd --list-all
215+
216+# Check Gordon is using correct port
217+grep "port =" ~/.config/gordon/gordon.toml
218+```
219+
220+### Service stops after logout
221+
222+```bash
223+# Enable linger
224+sudo loginctl enable-linger $USER
225+
226+# Verify
227+loginctl show-user $USER | grep Linger
228+```
229+
230+## Next Steps
231+
232+- [Configure Cloudflare](./cloudflare-setup.md)
233+- [Set up secrets](./secrets-pass.md)
234+- [Deploy your first app](/wiki/tutorials/first-deploy.md)
A wiki/index.md +43 -0
@@ -0,0 +1,43 @@
1+# Gordon Wiki
2+
3+Community tutorials, guides, and examples for Gordon.
4+
5+## Tutorials
6+
7+Step-by-step guides for common tasks:
8+
9+- [Deploy Your First App](./tutorials/first-deploy.md) - Get started with Gordon
10+- [Deploy a Next.js App](./tutorials/nextjs-deploy.md) - Full-stack JavaScript deployment
11+- [Add PostgreSQL to Your App](./tutorials/postgres-service.md) - Database attachments
12+- [Deploy Multi-Service Applications](./tutorials/multi-service-app.md) - Microservices setup
13+
14+## Guides
15+
16+In-depth guides for specific setups:
17+
18+- [Podman Rootless Setup](./guides/podman-rootless.md) - Enhanced security with rootless containers
19+- [Cloudflare DNS & Proxy Setup](./guides/cloudflare-setup.md) - HTTPS termination with Cloudflare
20+- [Using Pass for Secrets](./guides/secrets-pass.md) - Unix password manager integration
21+- [Using SOPS for Secrets](./guides/secrets-sops.md) - Encrypted secrets with SOPS
22+
23+## Examples
24+
25+Configuration examples explained:
26+
27+- [Minimal Configuration](./examples/minimal.md) - Simplest working setup
28+- [Production Configuration](./examples/production.md) - Full production setup
29+- [Development Configuration](./examples/development.md) - Local development setup
30+
31+## Quick Links
32+
33+- [Official Documentation](/docs/index.md)
34+- [Configuration Reference](/docs/config/index.md)
35+- [CLI Reference](/docs/cli/index.md)
36+- [GitHub Repository](https://github.com/bnema/gordon)
37+
38+## Contributing
39+
40+Found an error or want to add a tutorial? Contributions are welcome!
41+
42+- Edit documentation in the `docs/` and `wiki/` folders
43+- Submit a pull request to [github.com/bnema/gordon](https://github.com/bnema/gordon)
A wiki/tutorials/first-deploy.md +164 -0
@@ -0,0 +1,164 @@
1+# Deploy Your First App
2+
3+Deploy a simple web application to Gordon in under 10 minutes.
4+
5+## What You'll Learn
6+
7+- Building a container image
8+- Pushing to Gordon's registry
9+- Configuring routes
10+- Deploying your app
11+
12+## Prerequisites
13+
14+- Gordon running on your VPS
15+- Docker installed locally
16+- Domain configured (e.g., `app.mydomain.com`)
17+
18+## Steps
19+
20+### 1. Create a Simple App
21+
22+Create a new directory with a simple Node.js app:
23+
24+```bash
25+mkdir my-first-app
26+cd my-first-app
27+```
28+
29+Create `server.js`:
30+
31+```javascript
32+const http = require('http');
33+
34+const port = process.env.PORT || 3000;
35+
36+const server = http.createServer((req, res) => {
37+ res.writeHead(200, { 'Content-Type': 'text/html' });
38+ res.end(`
39+ <html>
40+ <body>
41+ <h1>Hello from Gordon!</h1>
42+ <p>Deployed at: ${new Date().toISOString()}</p>
43+ </body>
44+ </html>
45+ `);
46+});
47+
48+server.listen(port, '0.0.0.0', () => {
49+ console.log(`Server running on port ${port}`);
50+});
51+```
52+
53+Create `Dockerfile`:
54+
55+```dockerfile
56+FROM node:18-alpine
57+WORKDIR /app
58+COPY server.js .
59+ENV PORT=3000
60+EXPOSE 3000
61+CMD ["node", "server.js"]
62+```
63+
64+### 2. Build the Image
65+
66+```bash
67+docker build -t my-first-app .
68+```
69+
70+### 3. Tag for Gordon Registry
71+
72+Replace `registry.mydomain.com` with your Gordon registry domain:
73+
74+```bash
75+docker tag my-first-app registry.mydomain.com/my-first-app:latest
76+```
77+
78+### 4. Configure the Route
79+
80+On your Gordon server, edit `~/.config/gordon/gordon.toml`:
81+
82+```toml
83+[routes]
84+"app.mydomain.com" = "my-first-app:latest"
85+```
86+
87+Reload Gordon:
88+
89+```bash
90+gordon reload
91+```
92+
93+### 5. Push to Deploy
94+
95+Login to your registry (if auth enabled):
96+
97+```bash
98+docker login registry.mydomain.com
99+```
100+
101+Push the image:
102+
103+```bash
104+docker push registry.mydomain.com/my-first-app:latest
105+```
106+
107+### 6. Verify Deployment
108+
109+Visit `https://app.mydomain.com` in your browser.
110+
111+You should see "Hello from Gordon!" with the deployment timestamp.
112+
113+## Common Issues
114+
115+### "unauthorized" on push
116+
117+Enable registry authentication or check credentials:
118+
119+```bash
120+gordon auth token generate --subject deploy --expiry 0
121+docker login -u deploy -p <token> registry.mydomain.com
122+```
123+
124+### App not accessible
125+
126+1. Check the container is running:
127+ ```bash
128+ docker ps | grep my-first-app
129+ ```
130+
131+2. Check Gordon logs:
132+ ```bash
133+ gordon logs -f
134+ ```
135+
136+3. Ensure DNS points to your server
137+
138+### Wrong port
139+
140+If your app uses a different port, expose it in Dockerfile:
141+
142+```dockerfile
143+ENV PORT=8080
144+EXPOSE 8080
145+```
146+
147+## Updating Your App
148+
149+Make changes, then rebuild and push:
150+
151+```bash
152+# Edit server.js
153+docker build -t my-first-app .
154+docker tag my-first-app registry.mydomain.com/my-first-app:latest
155+docker push registry.mydomain.com/my-first-app:latest
156+```
157+
158+Gordon automatically deploys the update with zero downtime.
159+
160+## Next Steps
161+
162+- [Add environment variables](/docs/config/env.md)
163+- [Add a database](./postgres-service.md)
164+- [Set up CI/CD](/docs/deployment/github-actions.md)
A wiki/tutorials/index.md +27 -0
@@ -0,0 +1,27 @@
1+# Tutorials
2+
3+Step-by-step guides to help you get started with Gordon.
4+
5+## Getting Started
6+
7+- [Deploy Your First App](./first-deploy.md) - Deploy a simple web app in minutes
8+
9+## Framework Tutorials
10+
11+- [Deploy a Next.js App](./nextjs-deploy.md) - Full-stack Next.js deployment
12+- [Add PostgreSQL to Your App](./postgres-service.md) - Database attachments
13+
14+## Advanced
15+
16+- [Deploy Multi-Service Applications](./multi-service-app.md) - Microservices with shared services
17+
18+## Prerequisites
19+
20+Before starting tutorials, you should have:
21+
22+1. Gordon installed on a VPS
23+2. Docker or Podman on your local machine
24+3. Domain pointing to your VPS
25+4. Cloudflare (or similar) for HTTPS
26+
27+See [Installation Guide](/docs/installation.md) if you haven't set up Gordon yet.
A wiki/tutorials/postgres-service.md +205 -0
@@ -0,0 +1,205 @@
1+# Add PostgreSQL to Your App
2+
3+Add a PostgreSQL database as an attachment to your application.
4+
5+## What You'll Learn
6+
7+- Creating a custom PostgreSQL image with persistence
8+- Configuring attachments
9+- Connecting your app to the database
10+
11+## Prerequisites
12+
13+- Gordon running with an app deployed
14+- Basic understanding of [routes](/docs/config/routes.md) and [attachments](/docs/config/attachments.md)
15+
16+## Steps
17+
18+### 1. Create a Custom PostgreSQL Image
19+
20+Create `postgres.Dockerfile`:
21+
22+```dockerfile
23+FROM postgres:15-alpine
24+
25+# Persistent data directory
26+VOLUME ["/var/lib/postgresql/data"]
27+
28+# Default configuration (override via env file)
29+ENV POSTGRES_DB=myapp
30+ENV POSTGRES_USER=myapp
31+```
32+
33+### 2. Build and Push PostgreSQL Image
34+
35+```bash
36+docker build -f postgres.Dockerfile -t my-postgres .
37+docker tag my-postgres registry.mydomain.com/my-postgres:latest
38+docker push registry.mydomain.com/my-postgres:latest
39+```
40+
41+### 3. Configure the Attachment
42+
43+Edit `~/.config/gordon/gordon.toml`:
44+
45+```toml
46+[routes]
47+"app.mydomain.com" = "my-app:latest"
48+
49+[network_isolation]
50+enabled = true
51+
52+[attachments]
53+"app.mydomain.com" = ["my-postgres:latest"]
54+```
55+
56+Reload Gordon:
57+
58+```bash
59+gordon reload
60+```
61+
62+### 4. Configure Database Password
63+
64+Create an env file for your app at `~/.gordon/env/app_mydomain_com.env`:
65+
66+```bash
67+# Application settings
68+NODE_ENV=production
69+PORT=3000
70+
71+# Database connection
72+DATABASE_HOST=my-postgres
73+DATABASE_PORT=5432
74+DATABASE_NAME=myapp
75+DATABASE_USER=myapp
76+DATABASE_PASSWORD=your-secure-password
77+
78+# Full connection URL
79+DATABASE_URL=postgresql://myapp:your-secure-password@my-postgres:5432/myapp
80+```
81+
82+Create an env file for PostgreSQL at `~/.gordon/env/my-postgres.env`:
83+
84+```bash
85+POSTGRES_PASSWORD=your-secure-password
86+```
87+
88+### 5. Update Your Application
89+
90+Connect to the database using the service name:
91+
92+```javascript
93+// Node.js with pg
94+const { Pool } = require('pg');
95+
96+const pool = new Pool({
97+ host: process.env.DATABASE_HOST || 'my-postgres',
98+ port: process.env.DATABASE_PORT || 5432,
99+ database: process.env.DATABASE_NAME || 'myapp',
100+ user: process.env.DATABASE_USER || 'myapp',
101+ password: process.env.DATABASE_PASSWORD,
102+});
103+```
104+
105+```python
106+# Python with psycopg2
107+import os
108+import psycopg2
109+
110+conn = psycopg2.connect(
111+ host=os.environ.get('DATABASE_HOST', 'my-postgres'),
112+ port=os.environ.get('DATABASE_PORT', 5432),
113+ database=os.environ.get('DATABASE_NAME', 'myapp'),
114+ user=os.environ.get('DATABASE_USER', 'myapp'),
115+ password=os.environ['DATABASE_PASSWORD'],
116+)
117+```
118+
119+### 6. Rebuild and Deploy
120+
121+```bash
122+docker build -t my-app .
123+docker tag my-app registry.mydomain.com/my-app:latest
124+docker push registry.mydomain.com/my-app:latest
125+```
126+
127+## Verify Connection
128+
129+### Check Containers
130+
131+```bash
132+docker ps | grep gordon-app-mydomain
133+```
134+
135+You should see both your app and postgres containers.
136+
137+### Check Network
138+
139+```bash
140+docker network inspect gordon-app-mydomain-com
141+```
142+
143+Both containers should be in the same network.
144+
145+### Test Connection
146+
147+SSH to your server and test:
148+
149+```bash
150+docker exec -it gordon-app-mydomain-com-my-postgres psql -U myapp -d myapp
151+```
152+
153+## Using Secrets
154+
155+For production, use a secrets backend instead of plain text:
156+
157+```bash
158+# Store password in pass
159+pass insert myapp/db-password
160+```
161+
162+Update env file:
163+
164+```bash
165+DATABASE_PASSWORD=${pass:myapp/db-password}
166+POSTGRES_PASSWORD=${pass:myapp/db-password}
167+```
168+
169+## Data Persistence
170+
171+PostgreSQL data persists in a Docker volume:
172+
173+```bash
174+# List volumes
175+docker volume ls | grep postgres
176+
177+# Volume name: gordon-app-mydomain-com-my-postgres-var-lib-postgresql-data
178+```
179+
180+Data survives:
181+- Container restarts
182+- App updates
183+- Gordon reloads
184+
185+## Backup and Restore
186+
187+### Backup
188+
189+```bash
190+docker exec gordon-app-mydomain-com-my-postgres \
191+ pg_dump -U myapp myapp > backup.sql
192+```
193+
194+### Restore
195+
196+```bash
197+cat backup.sql | docker exec -i gordon-app-mydomain-com-my-postgres \
198+ psql -U myapp myapp
199+```
200+
201+## Next Steps
202+
203+- [Add Redis cache](./multi-service-app.md)
204+- [Configure secrets](/docs/config/secrets.md)
205+- [Network isolation](/docs/config/network-isolation.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