v2.0.0 ae3e752

README.md RawHistoryFolder

Gordon

Why Gordon?

You have a $5 VPS. You want to run multiple apps. You don't want complex CI/CD pipelines, expensive PaaS solutions, or overcomplicated orchestration.

Gordon is the missing piece that makes container deployment on budget VPS servers as simple as expensive PaaS solutions. One binary, one config file, unlimited apps.

Perfect For

  • Solo developers with multiple side projects on one VPS
  • Small teams who want Heroku-like simplicity without the cost
  • Agencies deploying client apps across VPS servers
  • Anyone tired of complex deployment pipelines

How It Works

# 1. Build & test locally
podman build -t myapp .
podman run -p 8080:8080 myapp  # Works? Great!

# 2. Push to deploy
podman tag myapp registry.mydomain.com/myapp:latest
podman push registry.mydomain.com/myapp:latest

# 3. That's it. Gordon deploys it automatically.

No build servers. No CI/CD complexity. If it runs on your machine, it runs in production.

Key Features

  • Local-First Development: Your machine is the build server
  • Built-in Container Registry: Your VPS becomes a private registry
  • Push-to-Deploy: New images deploy instantly
  • Multi-Domain Routing: Unlimited apps with automatic HTTPS via Cloudflare
  • Zero-Downtime Updates: Graceful container swaps
  • Network Isolation: Each app gets its own isolated network with attached services (databases, caches, etc.)
  • Auto-Route Creation (Optional): Push myapp.mydomain.com:latest → route created automatically
  • Auto-Volume Management: Zero-config persistent storage from Dockerfile VOLUME directives
  • Auto-Environment Injection: Dockerfile ENV directives automatically merge with your custom environment variables

Quick Start (5 minutes)

1. Setup VPS

# Install essentials on Ubuntu/Debian
sudo apt update && sudo apt install -y podman ufw

# Configure firewall
sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp 80/tcp 443/tcp
sudo ufw enable

2. Redirect Ports

# Redirect 80/443 to 8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

3. Enable Rootless Containers

echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
systemctl --user enable --now podman.socket

4. Configure Registry

mkdir -p ~/.config/containers
tee ~/.config/containers/registries.conf > /dev/null <<EOF
[registries.search]
registries = ['docker.io', 'registry.mydomain.com']

[registries.insecure]
registries = ['registry.mydomain.com']
EOF

5. Install Gordon

wget https://github.com/bnema/gordon/releases/latest/download/gordon-linux-amd64
chmod +x gordon-linux-amd64
sudo mv gordon-linux-amd64 /usr/local/bin/gordon

6. Configure Gordon

# Gordon creates a default config file on first run
# Edit it to set your registry domain and credentials:
# gordon.toml location: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/

# Key settings to update:
# - registry_domain = "registry.mydomain.com"
# - registry_auth username/password
# - routes for your domains

7. Point DNS

A    *.mydomain.com    →    YOUR_VPS_IP
A    mydomain.com      →    YOUR_VPS_IP

8. Create Service

mkdir -p ~/.config/systemd/user
tee ~/.config/systemd/user/gordon.service > /dev/null <<EOF
[Unit]
Description=Gordon Container Platform
After=podman.socket

[Service]
Type=simple
Restart=always
RestartSec=5
Environment=CONTAINER_HOST=unix://%t/podman/podman.sock
ExecStart=/usr/local/bin/gordon start
WorkingDirectory=%h

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable --now gordon
sudo loginctl enable-linger $USER

9. Deploy Your First App

# From your dev machine:
podman login registry.mydomain.com
# (Use the credentials from your gordon.toml)

podman tag myapp:latest registry.mydomain.com/myapp:latest
podman push registry.mydomain.com/myapp:latest
# Visit https://app.mydomain.com

Real-World Examples

Push-to-Deploy Workflow

# Build locally
podman build -t myapp .

# Push latest = instant deploy
podman push registry.mydomain.com/myapp:latest

Version Control & Rollbacks

Gordon supports multiple rollback strategies. Choose the approach that fits your workflow:

Method 1: Tag-Based Rollbacks (Simple)

# Tag and push a specific version
podman tag myapp:v1.0.1 registry.mydomain.com/myapp:v1.0.1
podman push registry.mydomain.com/myapp:v1.0.1

# Tag and push latest (v1.0.2 in this example)
podman tag myapp:v1.0.2 registry.mydomain.com/myapp:v1.0.2 && podman tag myapp:v1.0.2 registry.mydomain.com/myapp:latest
podman push registry.mydomain.com/myapp:v1.0.2 && podman push registry.mydomain.com/myapp:latest

# Rollback: Update gordon.toml and reload
# From: "app.mydomain.com" = "myapp:latest"
# To:   "app.mydomain.com" = "myapp:v1.0.1"
# Then: systemctl --user reload gordon

Method 2: Manifest Annotation Deployment (Advanced)

# Build and push your versioned images first
export VERSION=v1.0.1
podman build --tag myapp:$VERSION --tag registry.mydomain.com/myapp:$VERSION .
podman push registry.mydomain.com/myapp:$VERSION

export VERSION=v1.0.2
podman build --tag myapp:$VERSION --tag registry.mydomain.com/myapp:$VERSION .
podman push registry.mydomain.com/myapp:$VERSION

# Deploy v1.0.2 with version annotation
export VERSION=v1.0.2
podman manifest create myapp:latest
podman manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
podman manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
podman manifest push myapp:latest registry.mydomain.com/myapp:latest

# Rollback to v1.0.1 (just change the version)
export VERSION=v1.0.1
podman manifest create myapp:latest --amend
podman manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
podman manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
podman manifest push myapp:latest registry.mydomain.com/myapp:latest
# Gordon automatically deploys the specified version

Recommendation: Use Method 2 for instant deployments without config file edits. Method 1 is best for simple workflows.

Auto-Route Creation

[auto_route]
enabled = true
# Image name becomes route automatically
podman push registry.mydomain.com/staging.example.com:latest
# Creates: "staging.example.com" = "staging.example.com:latest"

Persistent Data with Volumes

Gordon automatically detects VOLUME directives in your Dockerfiles and creates persistent named volumes for your containers. Zero configuration required - just add VOLUME instructions to your Dockerfile.

How It Works

# In your Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# Gordon automatically creates volumes for these paths
VOLUME ["/app/data", "/app/uploads"]

EXPOSE 3000
CMD ["npm", "start"]

When you deploy this image, Gordon will:

  1. Detect the VOLUME ["/app/data", "/app/uploads"] directive
  2. Create named volumes like gordon-app-example-com-a1b2c3d4 (one per path)
  3. Mount them to your container automatically
  4. Preserve data across container restarts and updates

Volume Features

  • Automatic Detection: No config needed - Gordon reads VOLUME directives from your images
  • Persistent Storage: Data survives container restarts, updates, and VPS reboots
  • Zero Downtime: Volumes re-attach instantly when containers restart
  • Predictable Naming: gordon-{domain}-{path-hash} format for easy identification
  • Cross-Platform: Works with Docker and Podman

Example Workflows

Database Container

FROM postgres:15
# Gordon auto-creates persistent volume for PostgreSQL data
VOLUME ["/var/lib/postgresql/data"]

Web App with File Uploads

FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
# Gordon auto-creates persistent volume for user uploads
VOLUME ["/usr/share/nginx/html/uploads"]

Multi-Volume Application

FROM myapp:latest
# Gordon creates separate volumes for each path
VOLUME ["/app/data", "/app/logs", "/app/cache"]

Volume Configuration (Optional)

[volumes]
auto_create = true    # Default: true (set to false to disable)
prefix = "gordon"     # Default: "gordon" (volume name prefix)
preserve = true       # Default: true (keep volumes when removing containers)

Most users never need to touch this configuration - the defaults work perfectly for 99% of use cases.

Smart Environment & Volume Management

Gordon automatically handles both environment variables and persistent storage with zero configuration:

Environment Variables

  • Auto-Detection: Reads ENV directives from your Dockerfile
  • Smart Merging: Your .env files override Dockerfile ENV values
  • Secret Integration: Supports pass and SOPS for sensitive data

Persistent Volumes

  • Auto-Detection: Reads VOLUME directives from your Dockerfile
  • Zero Configuration: Creates and manages volumes automatically
  • Data Persistence: Survives container updates and reboots

See examples/ for detailed configuration examples.

Advanced Configuration

Full Config Structure (with default values)

# gordon.toml - Auto-generated on first run
[server]
port = 8080                          # Default: 8080
registry_port = 5000                 # Default: 5000  
registry_domain = "registry.mydomain.com"  # No default (required)
runtime = "auto"                     # Default: "auto" (auto, docker, podman, podman-rootless)
socket_path = ""                     # Default: "" (auto-detected)
data_dir = "~/.local/share/gordon"   # Default: ~/.local/share/gordon (or ./data for root)
ssl_email = ""                       # No default (optional)
# runtime auto-detects: docker → podman-rootless → podman
# Override with env: CONTAINER_HOST=unix:///custom/socket

[registry_auth]
enabled = true                       # Default: true
username = "admin"                   # No default (required when enabled)
password = "your-secure-password"    # No default (required when enabled)

[routes]
"app.mydomain.com" = "myapp:latest"  # Default: {} (empty map)
# Routes default to HTTPS unless prefixed with http://

# Network isolation - each app gets its own isolated network
[attachments]
"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]
# Services accessible via: postgres:5432, redis:6379 (internal DNS)
# Create custom Dockerfiles with VOLUME directives for persistence

[auto_route]
enabled = false                      # Default: false

[volumes]
auto_create = true                   # Default: true (automatically create volumes from VOLUME directives)
prefix = "gordon"                    # Default: "gordon" (volume name prefix)
preserve = true                      # Default: true (keep volumes when containers are removed)
# Volumes are auto-created from Dockerfile VOLUME directives
# Naming: gordon-{domain}-{path-hash} (e.g., gordon-app-example-com-a1b2c3d4)

[env]
dir = "{data_dir}/env"              # Default: {data_dir}/env
providers = ["pass", "sops"]        # Default: ["pass", "sops"]
# Example env file: ./data/env/app_mydomain_com.env
# NODE_ENV=production
# DATABASE_URL=postgresql://localhost:5432/prod
# API_KEY=${pass:company/api-key}      # Unix pass integration
# JWT_SECRET=${sops:secrets.yaml:jwt}  # SOPS integration

[logging]
enabled = true                       # Default: true
level = "info"                       # Default: "info" (debug, info, warn, error, fatal, panic)
dir = "{data_dir}/logs"             # Default: {data_dir}/logs
main_log_file = "gordon.log"        # Default: "gordon.log"
proxy_log_file = "proxy.log"        # Default: "proxy.log"
container_log_dir = "containers"    # Default: "containers"
max_size = 100                      # Default: 100 (MB before rotation)
max_backups = 3                     # Default: 3 (old files to keep)
max_age = 28                        # Default: 28 (days to keep old files)
compress = true                     # Default: true (gzip rotated logs)
# Creates: gordon.log, proxy.log, containers/*.log

FAQ

Q: How is this different from Traefik/Nginx Proxy Manager?
A: Those are just reverse proxies. Gordon is a complete deployment platform with built-in registry, automatic deployment, and container lifecycle management.

Q: Where do builds happen?
A: On YOUR machine. If it runs locally, it runs in production.

Q: How do I rollback?
A: Change the version annotation in your manifest and push, or edit gordon.toml. Takes seconds.

Q: Do I need Cloudflare?
A: Yes, for SSL certificates and DDoS protection.

Q: Resource requirements?
A: Runs on 1GB RAM VPS. Gordon uses <15MB RAM.

Q: How do I check if volumes are working?
A: Use docker volume ls or podman volume ls to see Gordon-managed volumes (named gordon-*).

Q: Can I disable automatic volumes?
A: Yes, set volumes.auto_create = false in your gordon.toml config.

Q: What happens to my data when I update containers?
A: Volumes are preserved by default. Your data persists across updates and reboots.

Q: How do I backup volume data?
A: Volumes are regular Docker/Podman volumes - use standard backup tools like docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .

Q: Do Dockerfile ENV directives work automatically?
A: Yes! Gordon reads ENV directives from your images and merges them with your custom .env files.

Q: What happens if I set the same environment variable in both Dockerfile and .env?
A: Your .env file always wins - custom environment variables override Dockerfile ENV directives.

Q: Can I see what environment variables are being applied?
A: Yes, check Gordon's logs - it shows both Dockerfile ENV and final merged environment variables.

Q: Do I need to rebuild my images to change environment variables?
A: No! Just update your .env files and reload Gordon. Dockerfile ENV provides defaults; .env provides overrides.

Philosophy

# Traditional CI/CD
push code → wait for build → hope it works → debug remotely → repeat

# The Gordon Way
build locally → test locally → push image → instant deploy

Your machine already works. Why replicate it in CI/CD?

License

GPL-3.0 - Use freely, contribute back.


source
1# Gordon
2
3<div align="center">
4  <img src="assets/gordon-mascot-hq-trsp.png" alt="Gordon Mascot" width="200">
5  <h3>The Smart Way to Deploy Containers on Your VPS</h3>
6  <p><em>Push image → Auto-deploy → Zero complexity</em></p>
7</div>
8
9## Why Gordon?
10
11**You have a $5 VPS. You want to run multiple apps. You don't want complex CI/CD pipelines, expensive PaaS solutions, or overcomplicated orchestration.**
12
13Gordon is the missing piece that makes container deployment on budget VPS servers as simple as expensive PaaS solutions. One binary, one config file, unlimited apps.
14
15### Perfect For
16- **Solo developers** with multiple side projects on one VPS
17- **Small teams** who want Heroku-like simplicity without the cost
18- **Agencies** deploying client apps across VPS servers
19- **Anyone** tired of complex deployment pipelines
20
21## How It Works
22
23```bash
24# 1. Build & test locally
25podman build -t myapp .
26podman run -p 8080:8080 myapp  # Works? Great!
27
28# 2. Push to deploy
29podman tag myapp registry.mydomain.com/myapp:latest
30podman push registry.mydomain.com/myapp:latest
31
32# 3. That's it. Gordon deploys it automatically.
33```
34
35**No build servers. No CI/CD complexity. If it runs on your machine, it runs in production.**
36
37## Key Features
38
39- **Local-First Development**: Your machine is the build server
40- **Built-in Container Registry**: Your VPS becomes a private registry
41- **Push-to-Deploy**: New images deploy instantly
42- **Multi-Domain Routing**: Unlimited apps with automatic HTTPS via Cloudflare
43- **Zero-Downtime Updates**: Graceful container swaps
44- **Network Isolation**: Each app gets its own isolated network with attached services (databases, caches, etc.)
45- **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically
46- **Auto-Volume Management**: Zero-config persistent storage from Dockerfile VOLUME directives
47- **Auto-Environment Injection**: Dockerfile ENV directives automatically merge with your custom environment variables
48
49## Quick Start (5 minutes)
50
51### 1. Setup VPS
52```bash
53# Install essentials on Ubuntu/Debian
54sudo apt update && sudo apt install -y podman ufw
55
56# Configure firewall
57sudo ufw --force enable
58sudo ufw default deny incoming
59sudo ufw default allow outgoing
60sudo ufw allow 22/tcp 80/tcp 443/tcp
61sudo ufw enable
62```
63
64### 2. Redirect Ports
65```bash
66# Redirect 80/443 to 8080
67sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
68sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
69sudo apt install -y iptables-persistent
70sudo netfilter-persistent save
71```
72
73### 3. Enable Rootless Containers
74```bash
75echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
76sudo sysctl -p
77sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
78systemctl --user enable --now podman.socket
79```
80
81### 4. Configure Registry
82```bash
83mkdir -p ~/.config/containers
84tee ~/.config/containers/registries.conf > /dev/null <<EOF
85[registries.search]
86registries = ['docker.io', 'registry.mydomain.com']
87
88[registries.insecure]
89registries = ['registry.mydomain.com']
90EOF
91```
92
93### 5. Install Gordon
94```bash
95wget https://github.com/bnema/gordon/releases/latest/download/gordon-linux-amd64
96chmod +x gordon-linux-amd64
97sudo mv gordon-linux-amd64 /usr/local/bin/gordon
98```
99
100### 6. Configure Gordon
101```bash
102# Gordon creates a default config file on first run
103# Edit it to set your registry domain and credentials:
104# gordon.toml location: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/
105
106# Key settings to update:
107# - registry_domain = "registry.mydomain.com"
108# - registry_auth username/password
109# - routes for your domains
110```
111
112### 7. Point DNS
113```
114A    *.mydomain.com    →    YOUR_VPS_IP
115A    mydomain.com      →    YOUR_VPS_IP
116```
117
118### 8. Create Service
119```bash
120mkdir -p ~/.config/systemd/user
121tee ~/.config/systemd/user/gordon.service > /dev/null <<EOF
122[Unit]
123Description=Gordon Container Platform
124After=podman.socket
125
126[Service]
127Type=simple
128Restart=always
129RestartSec=5
130Environment=CONTAINER_HOST=unix://%t/podman/podman.sock
131ExecStart=/usr/local/bin/gordon start
132WorkingDirectory=%h
133
134[Install]
135WantedBy=default.target
136EOF
137
138systemctl --user daemon-reload
139systemctl --user enable --now gordon
140sudo loginctl enable-linger $USER
141```
142
143### 9. Deploy Your First App
144```bash
145# From your dev machine:
146podman login registry.mydomain.com
147# (Use the credentials from your gordon.toml)
148
149podman tag myapp:latest registry.mydomain.com/myapp:latest
150podman push registry.mydomain.com/myapp:latest
151# Visit https://app.mydomain.com
152```
153
154## Real-World Examples
155
156### Push-to-Deploy Workflow
157```bash
158# Build locally
159podman build -t myapp .
160
161# Push latest = instant deploy
162podman push registry.mydomain.com/myapp:latest
163```
164
165### Version Control & Rollbacks
166
167Gordon supports multiple rollback strategies. Choose the approach that fits your workflow:
168
169#### Method 1: Tag-Based Rollbacks (Simple)
170```bash
171# Tag and push a specific version
172podman tag myapp:v1.0.1 registry.mydomain.com/myapp:v1.0.1
173podman push registry.mydomain.com/myapp:v1.0.1
174
175# Tag and push latest (v1.0.2 in this example)
176podman tag myapp:v1.0.2 registry.mydomain.com/myapp:v1.0.2 && podman tag myapp:v1.0.2 registry.mydomain.com/myapp:latest
177podman push registry.mydomain.com/myapp:v1.0.2 && podman push registry.mydomain.com/myapp:latest
178
179# Rollback: Update gordon.toml and reload
180# From: "app.mydomain.com" = "myapp:latest"
181# To:   "app.mydomain.com" = "myapp:v1.0.1"
182# Then: systemctl --user reload gordon
183```
184
185#### Method 2: Manifest Annotation Deployment (Advanced)
186```bash
187# Build and push your versioned images first
188export VERSION=v1.0.1
189podman build --tag myapp:$VERSION --tag registry.mydomain.com/myapp:$VERSION .
190podman push registry.mydomain.com/myapp:$VERSION
191
192export VERSION=v1.0.2
193podman build --tag myapp:$VERSION --tag registry.mydomain.com/myapp:$VERSION .
194podman push registry.mydomain.com/myapp:$VERSION
195
196# Deploy v1.0.2 with version annotation
197export VERSION=v1.0.2
198podman manifest create myapp:latest
199podman manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
200podman manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
201podman manifest push myapp:latest registry.mydomain.com/myapp:latest
202
203# Rollback to v1.0.1 (just change the version)
204export VERSION=v1.0.1
205podman manifest create myapp:latest --amend
206podman manifest add myapp:latest registry.mydomain.com/myapp:$VERSION
207podman manifest annotate myapp:latest --annotation version=$VERSION registry.mydomain.com/myapp:$VERSION
208podman manifest push myapp:latest registry.mydomain.com/myapp:latest
209# Gordon automatically deploys the specified version
210```
211
212**Recommendation**: Use Method 2 for instant deployments without config file edits. Method 1 is best for simple workflows.
213
214### Auto-Route Creation
215```toml
216[auto_route]
217enabled = true
218```
219
220```bash
221# Image name becomes route automatically
222podman push registry.mydomain.com/staging.example.com:latest
223# Creates: "staging.example.com" = "staging.example.com:latest"
224```
225
226## Persistent Data with Volumes
227
228Gordon automatically detects `VOLUME` directives in your Dockerfiles and creates persistent named volumes for your containers. **Zero configuration required** - just add `VOLUME` instructions to your Dockerfile.
229
230### How It Works
231
232```dockerfile
233# In your Dockerfile
234FROM node:18-alpine
235WORKDIR /app
236COPY package*.json ./
237RUN npm install
238COPY . .
239
240# Gordon automatically creates volumes for these paths
241VOLUME ["/app/data", "/app/uploads"]
242
243EXPOSE 3000
244CMD ["npm", "start"]
245```
246
247When you deploy this image, Gordon will:
2481. **Detect** the `VOLUME ["/app/data", "/app/uploads"]` directive
2492. **Create** named volumes like `gordon-app-example-com-a1b2c3d4` (one per path)
2503. **Mount** them to your container automatically
2514. **Preserve** data across container restarts and updates
252
253### Volume Features
254
255- **Automatic Detection**: No config needed - Gordon reads VOLUME directives from your images
256- **Persistent Storage**: Data survives container restarts, updates, and VPS reboots
257- **Zero Downtime**: Volumes re-attach instantly when containers restart
258- **Predictable Naming**: `gordon-{domain}-{path-hash}` format for easy identification
259- **Cross-Platform**: Works with Docker and Podman
260
261### Example Workflows
262
263#### Database Container
264```dockerfile
265FROM postgres:15
266# Gordon auto-creates persistent volume for PostgreSQL data
267VOLUME ["/var/lib/postgresql/data"]
268```
269
270#### Web App with File Uploads
271```dockerfile
272FROM nginx:alpine
273COPY dist/ /usr/share/nginx/html/
274# Gordon auto-creates persistent volume for user uploads
275VOLUME ["/usr/share/nginx/html/uploads"]
276```
277
278#### Multi-Volume Application
279```dockerfile
280FROM myapp:latest
281# Gordon creates separate volumes for each path
282VOLUME ["/app/data", "/app/logs", "/app/cache"]
283```
284
285### Volume Configuration (Optional)
286
287```toml
288[volumes]
289auto_create = true    # Default: true (set to false to disable)
290prefix = "gordon"     # Default: "gordon" (volume name prefix)
291preserve = true       # Default: true (keep volumes when removing containers)
292```
293
294**Most users never need to touch this configuration** - the defaults work perfectly for 99% of use cases.
295
296## Smart Environment & Volume Management
297
298Gordon automatically handles both environment variables and persistent storage with zero configuration:
299
300### Environment Variables
301- **Auto-Detection**: Reads `ENV` directives from your Dockerfile
302- **Smart Merging**: Your `.env` files override Dockerfile ENV values
303- **Secret Integration**: Supports `pass` and `SOPS` for sensitive data
304
305### Persistent Volumes  
306- **Auto-Detection**: Reads `VOLUME` directives from your Dockerfile
307- **Zero Configuration**: Creates and manages volumes automatically
308- **Data Persistence**: Survives container updates and reboots
309
310See [examples/](examples/) for detailed configuration examples.
311
312## Advanced Configuration
313
314### Full Config Structure (with default values)
315```toml
316# gordon.toml - Auto-generated on first run
317[server]
318port = 8080                          # Default: 8080
319registry_port = 5000                 # Default: 5000  
320registry_domain = "registry.mydomain.com"  # No default (required)
321runtime = "auto"                     # Default: "auto" (auto, docker, podman, podman-rootless)
322socket_path = ""                     # Default: "" (auto-detected)
323data_dir = "~/.local/share/gordon"   # Default: ~/.local/share/gordon (or ./data for root)
324ssl_email = ""                       # No default (optional)
325# runtime auto-detects: docker → podman-rootless → podman
326# Override with env: CONTAINER_HOST=unix:///custom/socket
327
328[registry_auth]
329enabled = true                       # Default: true
330username = "admin"                   # No default (required when enabled)
331password = "your-secure-password"    # No default (required when enabled)
332
333[routes]
334"app.mydomain.com" = "myapp:latest"  # Default: {} (empty map)
335# Routes default to HTTPS unless prefixed with http://
336
337# Network isolation - each app gets its own isolated network
338[attachments]
339"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]
340# Services accessible via: postgres:5432, redis:6379 (internal DNS)
341# Create custom Dockerfiles with VOLUME directives for persistence
342
343[auto_route]
344enabled = false                      # Default: false
345
346[volumes]
347auto_create = true                   # Default: true (automatically create volumes from VOLUME directives)
348prefix = "gordon"                    # Default: "gordon" (volume name prefix)
349preserve = true                      # Default: true (keep volumes when containers are removed)
350# Volumes are auto-created from Dockerfile VOLUME directives
351# Naming: gordon-{domain}-{path-hash} (e.g., gordon-app-example-com-a1b2c3d4)
352
353[env]
354dir = "{data_dir}/env"              # Default: {data_dir}/env
355providers = ["pass", "sops"]        # Default: ["pass", "sops"]
356# Example env file: ./data/env/app_mydomain_com.env
357# NODE_ENV=production
358# DATABASE_URL=postgresql://localhost:5432/prod
359# API_KEY=${pass:company/api-key}      # Unix pass integration
360# JWT_SECRET=${sops:secrets.yaml:jwt}  # SOPS integration
361
362[logging]
363enabled = true                       # Default: true
364level = "info"                       # Default: "info" (debug, info, warn, error, fatal, panic)
365dir = "{data_dir}/logs"             # Default: {data_dir}/logs
366main_log_file = "gordon.log"        # Default: "gordon.log"
367proxy_log_file = "proxy.log"        # Default: "proxy.log"
368container_log_dir = "containers"    # Default: "containers"
369max_size = 100                      # Default: 100 (MB before rotation)
370max_backups = 3                     # Default: 3 (old files to keep)
371max_age = 28                        # Default: 28 (days to keep old files)
372compress = true                     # Default: true (gzip rotated logs)
373# Creates: gordon.log, proxy.log, containers/*.log
374```
375
376## FAQ
377
378**Q: How is this different from Traefik/Nginx Proxy Manager?**  
379A: Those are just reverse proxies. Gordon is a complete deployment platform with built-in registry, automatic deployment, and container lifecycle management.
380
381**Q: Where do builds happen?**  
382A: On YOUR machine. If it runs locally, it runs in production.
383
384**Q: How do I rollback?**  
385A: Change the version annotation in your manifest and push, or edit gordon.toml. Takes seconds.
386
387**Q: Do I need Cloudflare?**  
388A: Yes, for SSL certificates and DDoS protection.
389
390**Q: Resource requirements?**  
391A: Runs on 1GB RAM VPS. Gordon uses <15MB RAM.
392
393**Q: How do I check if volumes are working?**  
394A: Use `docker volume ls` or `podman volume ls` to see Gordon-managed volumes (named `gordon-*`).
395
396**Q: Can I disable automatic volumes?**  
397A: Yes, set `volumes.auto_create = false` in your gordon.toml config.
398
399**Q: What happens to my data when I update containers?**  
400A: Volumes are preserved by default. Your data persists across updates and reboots.
401
402**Q: How do I backup volume data?**  
403A: Volumes are regular Docker/Podman volumes - use standard backup tools like `docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .`
404
405**Q: Do Dockerfile ENV directives work automatically?**  
406A: Yes! Gordon reads ENV directives from your images and merges them with your custom .env files.
407
408**Q: What happens if I set the same environment variable in both Dockerfile and .env?**  
409A: Your .env file always wins - custom environment variables override Dockerfile ENV directives.
410
411**Q: Can I see what environment variables are being applied?**  
412A: Yes, check Gordon's logs - it shows both Dockerfile ENV and final merged environment variables.
413
414**Q: Do I need to rebuild my images to change environment variables?**  
415A: No! Just update your .env files and reload Gordon. Dockerfile ENV provides defaults; .env provides overrides.
416
417## Philosophy
418
419```bash
420# Traditional CI/CD
421push code → wait for build → hope it works → debug remotely → repeat
422
423# The Gordon Way
424build locally → test locally → push image → instant deploy
425```
426
427Your machine already works. Why replicate it in CI/CD?
428
429## License
430
431GPL-3.0 - Use freely, contribute back.
432
433---
434
435<div align="center">
436  <p><strong>Stop overcomplicating container deployment.</strong></p>
437  <p>Star this repo if Gordon saves you money!</p>
438</div>

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