detached 432bbf5

docs(README): Revise deployment instructions and enhance feature descriptions

- Update sections for clarity, including renaming "Build Locally, Deploy Instantly" to "Build & Test Locally" and "Instant Rollbacks" to "Version Control & Rollbacks".
- Consolidate feature descriptions for better readability and remove outdated content.
- Streamline quick start guide and configuration instructions for improved user experience.
- Ensure consistency in terminology and formatting throughout the document.
commit
432bbf5b37c5125d79da1740d77cfe427c20deb4
author
bamen · · 2025-06-08 09:57 UTC
parent
33eb2a5
signature
unsigned
1 file · +98 -401
M README.md +98 -401
@@ -13,7 +13,6 @@
1313 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.
1414
1515 ### Perfect For
16-
1716 - **Solo developers** with multiple side projects on one VPS
1817 - **Small teams** who want Heroku-like simplicity without the cost
1918 - **Agencies** deploying client apps across VPS servers
@@ -21,10 +20,8 @@ Gordon is the missing piece that makes container deployment on budget VPS server
2120
2221 ## How It Works
2322
24-### Build Locally, Deploy Instantly
25-
2623 ```bash
27-# 1. Build & test on YOUR machine
24+# 1. Build & test locally
2825 podman build -t myapp .
2926 podman run -p 8080:8080 myapp # Works? Great!
3027
@@ -37,90 +34,49 @@ podman push registry.mydomain.com/myapp:latest
3734
3835 **No build servers. No CI/CD complexity. If it runs on your machine, it runs in production.**
3936
40-### Instant Rollbacks
41-
42-Something broke? Just change your config:
43-
44-```toml
45-# Before (in gordon.toml)
46-"app.mydomain.com" = "myapp:latest"
47-
48-# After - instant rollback!
49-"app.mydomain.com" = "myapp:v1.2.3"
50-```
51-
52-Save the file. Gordon redeploys the previous version. Problem solved in seconds.
53-
5437 ## Key Features
5538
56-### Local-First Development
57-**Your machine is the build server.** Test locally with your favorite container engine, push when ready. No waiting for remote builds.
58-
59-### Built-in Container Registry
60-Your VPS becomes a private container registry (Docker/Podman compatible). No Docker Hub subscription needed.
61-
62-### Push-to-Deploy Magic
63-Gordon watches for new images and deploys them instantly to configured domains.
64-
65-### Version Control Built-In
66-Keep multiple versions in your registry. Switch between them instantly by editing the config.
67-
68-### Multi-Domain Routing
69-Run unlimited apps on one server. Each gets its own domain with automatic HTTPS via Cloudflare.
70-
71-### Zero-Downtime Updates
72-Push new versions anytime. Gordon handles graceful container swaps.
73-
74-### Automatic Deployment
75-Containers deploy instantly when you push new images. No manual deployment steps needed.
76-
77-### Auto-Route Creation
78-Automatically create routes from image names containing domains. Push an image named like `myapp.mydomain.com:latest` and Gordon creates the route automatically when enabled.
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+- **Auto-Route Creation**: Push `myapp.mydomain.com:latest` → route created automatically
7945
8046 ## Quick Start (5 minutes)
8147
82-### 1. Get a VPS, Install Podman and UFW
48+### 1. Setup VPS
8349 ```bash
84-# Any VPS provider: DigitalOcean, Linode, Vultr, Hetzner
85-# Ubuntu/Debian recommended
86-
87-# Install Essentials
88-sudo apt update
89-sudo apt install -y podman ufw
50+# Install essentials on Ubuntu/Debian
51+sudo apt update && sudo apt install -y podman ufw
9052
91-# Configure UFW firewall first
53+# Configure firewall
9254 sudo ufw --force enable
9355 sudo ufw default deny incoming
9456 sudo ufw default allow outgoing
95-sudo ufw allow 22/tcp
96-sudo ufw allow 80/tcp
97-sudo ufw allow 443/tcp
57+sudo ufw allow 22/tcp 80/tcp 443/tcp
9858 sudo ufw enable
9959 ```
100-### 2. Redirect Ports 80/443 to 8080
60+
61+### 2. Redirect Ports
10162 ```bash
102-# Simple iptables redirect
63+# Redirect 80/443 to 8080
10364 sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
10465 sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
105-
106-# Make rules persistent across reboots
10766 sudo apt install -y iptables-persistent
10867 sudo netfilter-persistent save
10968 ```
110-### 3. Enable rootless mode for enhanced security
111-```
69+
70+### 3. Enable Rootless Containers
71+```bash
11272 echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
11373 sudo sysctl -p
114-
115-# Configure user for rootless containers
11674 sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
117-
118-# Start user services (no root required!)
11975 systemctl --user enable --now podman.socket
12076 ```
12177
122-### 4. Configure registries for your Gordon registry
123-```
78+### 4. Configure Registry
79+```bash
12480 mkdir -p ~/.config/containers
12581 tee ~/.config/containers/registries.conf > /dev/null <<EOF
12682 [registries.search]
@@ -128,9 +84,6 @@ registries = ['docker.io', 'registry.mydomain.com']
12884
12985 [registries.insecure]
13086 registries = ['registry.mydomain.com']
131-
132-[registries.block]
133-registries = []
13487 EOF
13588 ```
13689
@@ -141,48 +94,31 @@ chmod +x gordon-linux-amd64
14194 sudo mv gordon-linux-amd64 /usr/local/bin/gordon
14295 ```
14396
144-### 6. Create Config file
145-```toml
146-# Gordon searches for `gordon.toml` config in: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/
147-# (or use --config flag)
148-[server]
149-port = 8080
150-registry_domain = "registry.mydomain.com"
151-runtime = "podman-rootless" # Can be auto, docker, podman, podman-rootless
152-
153-[registry_auth]
154-enabled = true
155-username = "admin"
156-password = "your-secure-password"
157-
158-[routes]
159-"app.mydomain.com" = "myapp:latest"
160-"api.mydomain.com" = "api:v1"
161-"blog.mydomain.com" = "wordpress:latest"
162-
163-[auto_route]
164-enabled = false # Enable automatic route creation
97+### 6. Configure Gordon
98+```bash
99+# Gordon creates a default config file on first run
100+# Edit it to set your registry domain and credentials:
101+# gordon.toml location: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/
165102
166-# Custom config file location (optional):
167-# gordon --config /path/to/custom.toml start
103+# Key settings to update:
104+# - registry_domain = "registry.mydomain.com"
105+# - registry_auth username/password
106+# - routes for your domains
168107 ```
169108
170-### 7. Point Cloudflare DNS
109+### 7. Point DNS
171110 ```
172111 A *.mydomain.com → YOUR_VPS_IP
173112 A mydomain.com → YOUR_VPS_IP
174113 ```
175114
176-### 8. Create Systemd Service (Rootless)
115+### 8. Create Service
177116 ```bash
178-# Create user systemd service (no root privileges needed!)
179117 mkdir -p ~/.config/systemd/user
180-
181118 tee ~/.config/systemd/user/gordon.service > /dev/null <<EOF
182119 [Unit]
183-Description=Gordon Container Platform (Rootless)
120+Description=Gordon Container Platform
184121 After=podman.socket
185-Requires=podman.socket
186122
187123 [Service]
188124 Type=simple
@@ -196,25 +132,17 @@ WorkingDirectory=%h
196132 WantedBy=default.target
197133 EOF
198134
199-# Enable and start the user service
200135 systemctl --user daemon-reload
201136 systemctl --user enable --now gordon
202-
203-# Enable lingering to start service on boot
204137 sudo loginctl enable-linger $USER
205-
206-# Check service status
207-systemctl --user status gordon
208138 ```
209139
210-### 8. Deploy Your First App
140+### 9. Deploy Your First App
211141 ```bash
212-# Now from your local development machine:
213-# Authenticate with your Gordon registry
142+# From your dev machine:
214143 podman login registry.mydomain.com
215-# Use the username/password from your gordon.toml config
144+# (Use the credentials from your gordon.toml)
216145
217-# Deploy your first app (using podman)
218146 podman tag myapp:latest registry.mydomain.com/myapp:latest
219147 podman push registry.mydomain.com/myapp:latest
220148 # Visit https://app.mydomain.com
@@ -222,342 +150,111 @@ podman push registry.mydomain.com/myapp:latest
222150
223151 ## Real-World Examples
224152
225-### Deploy a Node.js App (The "Push-to-Deploy" Way)
226-This is the simplest way to deploy. Your production environment is always in sync with your `latest` tag.
227-
153+### Push-to-Deploy Workflow
228154 ```bash
229-# 1. Build and test locally first
155+# Build locally
230156 podman build -t myapp .
231-podman run -p 3000:3000 myapp # Test it!
232157
233-# 2. In gordon.toml, point your domain to the 'latest' tag
234-# "app.mydomain.com" = "myapp:latest"
235-
236-# 3. Tag your image as 'latest' for your registry
237-podman tag myapp registry.mydomain.com/myapp:latest
238-
239-# 4. Push to deploy
158+# Push latest = instant deploy
240159 podman push registry.mydomain.com/myapp:latest
241-
242-# Gordon automatically detects the push and deploys the new 'latest' version.
243160 ```
244161
245-### Smart Versioning Strategy
246-For more control, especially in production, you can use a combination of version tags and the `latest` tag. This makes rollbacks trivial and allows for staging environments.
247-
162+### Version Control & Rollbacks
248163 ```bash
249-# 1. Always push a specific version tag first. This creates a history.
250-podman tag myapp registry.mydomain.com/myapp:v1.0.1
164+# Push versioned releases
251165 podman push registry.mydomain.com/myapp:v1.0.1
252166
253-# 2. Test the new version on a staging domain.
254-# In gordon.toml:
255-"staging.mydomain.com" = "myapp:v1.0.1"
256-# Get your production route ready :
257-"app.mydomain.com" = "myapp:latest"
258-
259-# 3. Once tested and confirmed, promote it to production by updating 'latest'.
260-podman tag myapp registry.mydomain.com/myapp:latest
261-podman push registry.mydomain.com/myapp:latest
262-```
263-
264-### Instant Rollback When Things Break
265-```toml
266-# Production broke after the latest push?
267-# Just edit gordon.toml to point to a previously pushed, stable version tag:
268-
269-# From:
270-"app.mydomain.com" = "myapp:latest"
271-
272-# To:
273-"app.mydomain.com" = "myapp:v1.0.0"
274-
275-# Save the file. Gordon redeploys the stable version in seconds. No scripts, no drama.
167+# Instant rollback in gordon.toml:
168+# From: "app.mydomain.com" = "myapp:latest"
169+# To: "app.mydomain.com" = "myapp:v1.0.0"
276170 ```
277171
278-### Auto-Route Creation for Testing
279-
280-Gordon can automatically create routes from image names that contain valid domain names. This is perfect for testing deployments without manually editing config files.
281-
282-#### Enable Auto-Routes
172+### Auto-Route Creation
283173 ```toml
284174 [auto_route]
285-enabled = true # Default: false
175+enabled = true
286176 ```
287177
288-#### How It Works
289-When you push an image with a domain name as the image name, Gordon automatically creates a route:
290-
291178 ```bash
292-# Build your app normally
293-podman build -t myapp .
294-
295-# Tag with domain name as the image name
296-podman tag myapp:latest registry.mydomain.com/myapp.mydomain.dev:latest
297-podman push registry.mydomain.com/myapp.mydomain.dev:latest
298-
299-# Gordon automatically creates:
300-# "myapp.mydomain.dev" = "myapp.mydomain.dev:latest"
301-# The route is added to your config file and deployed instantly!
179+# Image name becomes route automatically
180+podman push registry.mydomain.com/staging.example.com:latest
181+# Creates: "staging.example.com" = "staging.example.com:latest"
302182 ```
303183
304-#### Perfect for Testing Workflows
305-```bash
306-# Test different domains for the same app
307-podman build -t myapp .
308-
309-# Push to different test domains using image names
310-podman tag myapp:latest registry.mydomain.com/staging.example.com:latest
311-podman push registry.mydomain.com/staging.example.com:latest
184+## Advanced Configuration
312185
313-podman tag myapp:latest registry.mydomain.com/demo.example.com:v1.0.0
314-podman push registry.mydomain.com/demo.example.com:v1.0.0
186+### Full Config Structure
187+```toml
188+# gordon.toml - Auto-generated on first run
189+[server]
190+port = 8080
191+registry_domain = "registry.mydomain.com"
192+runtime = "podman-rootless" # auto, docker, podman, podman-rootless
193+# runtime auto-detects: docker → podman-rootless → podman
194+# Override with env: CONTAINER_HOST=unix:///custom/socket
315195
316-# Both automatically get their own routes:
317-# staging.example.com -> staging.example.com:latest
318-# demo.example.com -> demo.example.com:v1.0.0
319-```
196+[registry_auth]
197+enabled = true
198+username = "admin"
199+password = "your-secure-password"
320200
321-#### Domain Validation
322-Gordon only creates auto-routes for valid domain names in image names:
323-- ✅ `api.example.com:latest` → Valid subdomain name, route created
324-- ✅ `myapp.dev:v1.0.0` → Valid domain name, route created
325-- ❌ `myapp:latest` → Not a domain, ignored
326-- ❌ `myapp:v1.0.0` → Not a domain, ignored
327-- ❌ `localhost:latest` → Not a valid domain, ignored
201+[routes]
202+"app.mydomain.com" = "myapp:latest"
328203
329-#### Route Precedence
330-- Existing routes in `[routes]` are never overwritten
331-- Auto-routes are saved to your config file permanently
332-- You can manually edit or remove auto-created routes anytime
204+[auto_route]
205+enabled = false # Auto-create routes from image names
333206
207+[env]
208+dir = "./data/env" # Gordon auto-creates env files here
209+providers = ["pass", "sops"] # Optional secret management
210+# Example env file: ./data/env/app_mydomain_com.env
211+# NODE_ENV=production
212+# DATABASE_URL=postgresql://localhost:5432/prod
213+# API_KEY=${pass:company/api-key} # Unix pass integration
214+# JWT_SECRET=${sops:secrets.yaml:jwt} # SOPS integration
215+
216+[logging]
217+enabled = false
218+level = "info" # trace, debug, info, warn, error
219+dir = "./logs" # Log directory
220+main_log_file = "gordon.log" # Main app log
221+proxy_log_file = "proxy.log" # HTTP traffic log
222+container_log_dir = "containers" # Container logs subdirectory
223+max_size = 100 # MB before rotation
224+max_backups = 3 # Number of old files to keep
225+max_age = 28 # Days to keep old files
226+compress = true # Gzip rotated logs
227+# Creates: gordon.log, proxy.log, containers/*.log
228+```
334229
335230 ## FAQ
336231
337232 **Q: How is this different from Traefik/Nginx Proxy Manager?**
338-A: Those are just reverse proxies - you still need to manually start/stop containers. Gordon is a complete deployment platform: built-in registry, automatic deployment on push, and container lifecycle management. It's the difference between a router and a full PaaS.
233+A: Those are just reverse proxies. Gordon is a complete deployment platform with built-in registry, automatic deployment, and container lifecycle management.
339234
340235 **Q: Where do builds happen?**
341-A: On YOUR machine. If it runs locally, it'll run in production. No mysterious build server issues.
342-
343-**Q: How do I handle broken deployments?**
344-A: Just edit gordon.toml to point to a previous version. Rollback takes seconds, not minutes.
236+A: On YOUR machine. If it runs locally, it runs in production.
345237
346-**Q: Do I need CI/CD?**
347-A: Nope! Your computer is the CI/CD. Build, test locally, push when ready. Keep it stupid simple.
238+**Q: How do I rollback?**
239+A: Edit gordon.toml to point to a previous version. Takes seconds.
348240
349241 **Q: Do I need Cloudflare?**
350-A: Yes for SSL certificates. Gordon doesn't handle Let's Encrypt yet, so Cloudflare provides the SSL termination and DDoS protection.
351-
352-**Q: Can I run databases?**
353-A: You can in theory, but exposing databases on the internet is risky. Database/app isolation is coming soon for safer database deployments.
242+A: Yes, for SSL certificates and DDoS protection.
354243
355244 **Q: Resource requirements?**
356-A: Runs comfortably on 1GB RAM VPS. Gordon itself uses <15MB of RAM.
357-
358-**Q: What about secrets?**
359-A: Gordon provides built-in environment variable support with secret management via `pass` and `sops`. See the Environment Variables section below.
360-
361-**Q: How does auto-route creation work?**
362-A: When enabled, Gordon watches for pushed images with domain names as image names (like `staging.example.com:latest`) and automatically creates routes. Perfect for testing deployments without manual config edits.
245+A: Runs on 1GB RAM VPS. Gordon uses <15MB RAM.
363246
364-## Philosophy: Build Local, Deploy Simple
365-
366-### Why No Build Servers?
367-
368-1. **Your machine already works** - Why replicate your dev environment in CI/CD?
369-2. **Instant feedback** - Build errors show up immediately, not after pushing
370-3. **Perfect reproducibility** - "Works on my machine" becomes a feature, not a bug
371-4. **Zero build queues** - Deploy as fast as your internet can push
372-
373-### The Gordon Way
247+## Philosophy
374248
375249 ```bash
376250 # Traditional CI/CD
377251 push code → wait for build → hope it works → debug remotely → repeat
378252
379-# The Gordon Way (with Podman)
380-podman build locally → test locally → push image → instant deploy
381-```
382-
383-## Architecture
384-
385-- **Event-Driven**: Push events trigger deployments automatically
386-- **Config Hot-Reload**: Edit gordon.toml, changes apply instantly
387-- **Single Binary**: No dependencies except Docker
388-- **Stateless**: Configuration is the only state
389-- **Fast**: Written in Go for minimal overhead
390-
391-## Advanced Usage
392-
393-### Container Runtime Configuration
394-```toml
395-[server]
396-runtime = "auto" # auto, docker, podman, podman-rootless
397-socket_path = "" # optional custom socket path
398-
399-# Examples:
400-# runtime = "docker"
401-# runtime = "podman"
402-# socket_path = "unix:///run/user/1000/podman/podman.sock"
403-```
404-
405-### Environment Override
406-```bash
407-# Override container socket with environment variable
408-export CONTAINER_HOST=unix:///custom/path/container.sock
409-export CONTAINER_HOST=tcp://remote-docker:2376
410-
411-# Works for both Docker and Podman
412-gordon start
413-```
414-
415-### Runtime Auto-Detection
416-Gordon automatically detects available container runtimes in this order:
417-1. **Docker** (`/var/run/docker.sock`)
418-2. **Podman rootless** (`$XDG_RUNTIME_DIR/podman/podman.sock`)
419-3. **Podman root** (`/run/podman/podman.sock`)
420-
421-### Environment Variables & Secret Management
422-
423-Gordon supports per-route environment variables with secure secret management.
424-
425-#### Configuration
426-```toml
427-[env]
428-# Directory where .env files are stored for each route
429-dir = "./data/env" # Default location
430-# Secret providers for secure credential management
431-providers = ["pass", "sops"] # Unix password manager and SOPS
432-```
433-
434-#### Creating Environment Files
435-Gordon automatically creates empty `.env` files for all configured routes when it starts up. The files are named after the domain with special characters replaced by underscores:
436-
437-```bash
438-# Gordon automatically creates these files for you:
439-# For route "app.mydomain.com"
440-./data/env/app_mydomain_com.env
441-
442-# For route "api-v2.mydomain.com"
443-./data/env/api-v2_mydomain_com.env
444-```
445-
446-#### Environment File Format
447-When Gordon creates env files automatically, they include helpful comments and examples.
448-
449-**Security:** Gordon creates the env directory with `0700` permissions and env files with `0600` permissions (owner read/write only) to protect sensitive information.
450-
451-```bash
452-# ./data/env/app_mydomain_com.env (automatically created by Gordon)
453-# Environment variables for route: app.mydomain.com
454-# Image: myapp:latest
455-
456-NODE_ENV=production
457-PORT=3000
458-DATABASE_URL=postgresql://localhost:5432/prod
459-
460-# Secret references (resolved at container start)
461-API_KEY=${pass:company/api-key}
462-JWT_SECRET=${sops:secrets.yaml:app.jwt_secret}
463-```
464-
465-#### Secret Providers
466-
467-**Unix Password Manager (pass)**
468-```bash
469-# Store secrets with pass
470-pass insert company/api-key
471-pass insert company/database-password
472-
473-# Reference in .env files
474-API_KEY=${pass:company/api-key}
475-DB_PASSWORD=${pass:company/database-password}
476-```
477-
478-**SOPS (Secrets OPerationS)**
479-```bash
480-# Create encrypted YAML file
481-sops secrets.yaml
482-
483-# secrets.yaml content:
484-# app:
485-# jwt_secret: "super-secret-key"
486-# database_url: "postgresql://user:pass@db:5432/prod"
487-
488-# Reference in .env files
489-JWT_SECRET=${sops:secrets.yaml:app.jwt_secret}
490-DATABASE_URL=${sops:secrets.yaml:app.database_url}
491-```
492-
493-#### How It Works
494-1. Gordon loads the `.env` file for each route during container deployment
495-2. Secret references (`${provider:path}`) are resolved using the configured providers
496-3. Environment variables are passed to the container at startup
497-4. Missing env files are silently ignored (optional env vars)
498-5. Secret resolution failures cause deployment to fail for security
499-
500-#### Examples
501-
502-**Development Setup**
503-```toml
504-# gordon.toml
505-[env]
506-dir = "./dev-data/env"
507-providers = ["pass"] # Optional for dev secrets
508-
509-[routes]
510-"app.local" = "myapp:latest"
253+# The Gordon Way
254+build locally → test locally → push image → instant deploy
511255 ```
512256
513-```bash
514-# ./dev-data/env/app_local.env
515-NODE_ENV=development
516-DEBUG=true
517-DATABASE_URL=postgresql://localhost:5432/dev
518-API_KEY=${pass:dev/api-key} # Optional: use pass for dev secrets
519-```
520-
521-**Production Setup**
522-```toml
523-# gordon.toml
524-[env]
525-dir = "/var/lib/gordon/env"
526-providers = ["pass", "sops"]
527-
528-[routes]
529-"app.company.com" = "company-app:v2.1.0"
530-```
531-
532-```bash
533-# /var/lib/gordon/env/app_company_com.env
534-NODE_ENV=production
535-DATABASE_URL=postgresql://user:${pass:company/db-password}@db:5432/prod
536-API_KEY=${sops:company-secrets.yaml:api.key}
537-JWT_SECRET=${sops:company-secrets.yaml:jwt.secret}
538-```
539-
540-### Port Binding
541-Gordon requires your Dockerfile to explicitly expose ports using the `EXPOSE` instruction. When multiple ports are exposed, Gordon will use the first exposed port for HTTP traffic.
542-
543-Example Dockerfile:
544-```
545-ENTRYPOINT ["/myapp"]
546-EXPOSE 8080
547-```
548-
549-### Registry Operations
550-```bash
551-# List images
552-curl -u admin:password https://registry.mydomain.com/v2/_catalog
553-
554-# List tags
555-curl -u admin:password https://registry.mydomain.com/v2/myapp/tags/list
556-```
557-
558-## Contributing
559-
560-Gordon is open source and we love contributions! Check out our [issues](https://github.com/yourusername/gordon/issues) or submit a PR.
257+Your machine already works. Why replicate it in CI/CD?
561258
562259 ## License
563260
@@ -13,7 +13,6 @@
13 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.13 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.
14 14
15 ### Perfect For15 ### Perfect For
16-
17 - **Solo developers** with multiple side projects on one VPS16 - **Solo developers** with multiple side projects on one VPS
18 - **Small teams** who want Heroku-like simplicity without the cost17 - **Small teams** who want Heroku-like simplicity without the cost
19 - **Agencies** deploying client apps across VPS servers18 - **Agencies** deploying client apps across VPS servers
@@ -21,10 +20,8 @@ Gordon is the missing piece that makes container deployment on budget VPS server
21 20
22 ## How It Works21 ## How It Works
23 22
24-### Build Locally, Deploy Instantly
25-
26 ```bash23 ```bash
27-# 1. Build & test on YOUR machine24+# 1. Build & test locally
28 podman build -t myapp .25 podman build -t myapp .
29 podman run -p 8080:8080 myapp # Works? Great!26 podman run -p 8080:8080 myapp # Works? Great!
30 27
@@ -37,90 +34,49 @@ podman push registry.mydomain.com/myapp:latest
37 34
38 **No build servers. No CI/CD complexity. If it runs on your machine, it runs in production.**35 **No build servers. No CI/CD complexity. If it runs on your machine, it runs in production.**
39 36
40-### Instant Rollbacks
41-
42-Something broke? Just change your config:
43-
44-```toml
45-# Before (in gordon.toml)
46-"app.mydomain.com" = "myapp:latest"
47-
48-# After - instant rollback!
49-"app.mydomain.com" = "myapp:v1.2.3"
50-```
51-
52-Save the file. Gordon redeploys the previous version. Problem solved in seconds.
53-
54 ## Key Features37 ## Key Features
55 38
56-### Local-First Development39+- **Local-First Development**: Your machine is the build server
57-**Your machine is the build server.** Test locally with your favorite container engine, push when ready. No waiting for remote builds.40+- **Built-in Container Registry**: Your VPS becomes a private registry
58-41+- **Push-to-Deploy**: New images deploy instantly
59-### Built-in Container Registry42+- **Multi-Domain Routing**: Unlimited apps with automatic HTTPS via Cloudflare
60-Your VPS becomes a private container registry (Docker/Podman compatible). No Docker Hub subscription needed.43+- **Zero-Downtime Updates**: Graceful container swaps
61-44+- **Auto-Route Creation**: Push `myapp.mydomain.com:latest` → route created automatically
62-### Push-to-Deploy Magic
63-Gordon watches for new images and deploys them instantly to configured domains.
64-
65-### Version Control Built-In
66-Keep multiple versions in your registry. Switch between them instantly by editing the config.
67-
68-### Multi-Domain Routing
69-Run unlimited apps on one server. Each gets its own domain with automatic HTTPS via Cloudflare.
70-
71-### Zero-Downtime Updates
72-Push new versions anytime. Gordon handles graceful container swaps.
73-
74-### Automatic Deployment
75-Containers deploy instantly when you push new images. No manual deployment steps needed.
76-
77-### Auto-Route Creation
78-Automatically create routes from image names containing domains. Push an image named like `myapp.mydomain.com:latest` and Gordon creates the route automatically when enabled.
79 45
80 ## Quick Start (5 minutes)46 ## Quick Start (5 minutes)
81 47
82-### 1. Get a VPS, Install Podman and UFW48+### 1. Setup VPS
83 ```bash49 ```bash
84-# Any VPS provider: DigitalOcean, Linode, Vultr, Hetzner50+# Install essentials on Ubuntu/Debian
85-# Ubuntu/Debian recommended51+sudo apt update && sudo apt install -y podman ufw
86-
87-# Install Essentials
88-sudo apt update
89-sudo apt install -y podman ufw
90 52
91-# Configure UFW firewall first53+# Configure firewall
92 sudo ufw --force enable54 sudo ufw --force enable
93 sudo ufw default deny incoming55 sudo ufw default deny incoming
94 sudo ufw default allow outgoing56 sudo ufw default allow outgoing
95-sudo ufw allow 22/tcp57+sudo ufw allow 22/tcp 80/tcp 443/tcp
96-sudo ufw allow 80/tcp
97-sudo ufw allow 443/tcp
98 sudo ufw enable58 sudo ufw enable
99 ```59 ```
100-### 2. Redirect Ports 80/443 to 808060+
61+### 2. Redirect Ports
101 ```bash62 ```bash
102-# Simple iptables redirect63+# Redirect 80/443 to 8080
103 sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 808064 sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
104 sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 808065 sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
105-
106-# Make rules persistent across reboots
107 sudo apt install -y iptables-persistent66 sudo apt install -y iptables-persistent
108 sudo netfilter-persistent save67 sudo netfilter-persistent save
109 ```68 ```
110-### 3. Enable rootless mode for enhanced security69+
111-```70+### 3. Enable Rootless Containers
71+```bash
112 echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf72 echo 'user.max_user_namespaces=28633' | sudo tee -a /etc/sysctl.conf
113 sudo sysctl -p73 sudo sysctl -p
114-
115-# Configure user for rootless containers
116 sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER74 sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
117-
118-# Start user services (no root required!)
119 systemctl --user enable --now podman.socket75 systemctl --user enable --now podman.socket
120 ```76 ```
121 77
122-### 4. Configure registries for your Gordon registry78+### 4. Configure Registry
123-```79+```bash
124 mkdir -p ~/.config/containers80 mkdir -p ~/.config/containers
125 tee ~/.config/containers/registries.conf > /dev/null <<EOF81 tee ~/.config/containers/registries.conf > /dev/null <<EOF
126 [registries.search]82 [registries.search]
@@ -128,9 +84,6 @@ registries = ['docker.io', 'registry.mydomain.com']
128 84
129 [registries.insecure]85 [registries.insecure]
130 registries = ['registry.mydomain.com']86 registries = ['registry.mydomain.com']
131-
132-[registries.block]
133-registries = []
134 EOF87 EOF
135 ```88 ```
136 89
@@ -141,48 +94,31 @@ chmod +x gordon-linux-amd64
141 sudo mv gordon-linux-amd64 /usr/local/bin/gordon94 sudo mv gordon-linux-amd64 /usr/local/bin/gordon
142 ```95 ```
143 96
144-### 6. Create Config file97+### 6. Configure Gordon
145-```toml98+```bash
146-# Gordon searches for `gordon.toml` config in: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/99+# Gordon creates a default config file on first run
147-# (or use --config flag)100+# Edit it to set your registry domain and credentials:
148-[server]101+# gordon.toml location: ./ → ~/.config/gordon/ → ~/.gordon/ → ~/ → /etc/gordon/
149-port = 8080
150-registry_domain = "registry.mydomain.com"
151-runtime = "podman-rootless" # Can be auto, docker, podman, podman-rootless
152-
153-[registry_auth]
154-enabled = true
155-username = "admin"
156-password = "your-secure-password"
157-
158-[routes]
159-"app.mydomain.com" = "myapp:latest"
160-"api.mydomain.com" = "api:v1"
161-"blog.mydomain.com" = "wordpress:latest"
162-
163-[auto_route]
164-enabled = false # Enable automatic route creation
165 102
166-# Custom config file location (optional):103+# Key settings to update:
167-# gordon --config /path/to/custom.toml start104+# - registry_domain = "registry.mydomain.com"
105+# - registry_auth username/password
106+# - routes for your domains
168 ```107 ```
169 108
170-### 7. Point Cloudflare DNS109+### 7. Point DNS
171 ```110 ```
172 A *.mydomain.com → YOUR_VPS_IP111 A *.mydomain.com → YOUR_VPS_IP
173 A mydomain.com → YOUR_VPS_IP112 A mydomain.com → YOUR_VPS_IP
174 ```113 ```
175 114
176-### 8. Create Systemd Service (Rootless)115+### 8. Create Service
177 ```bash116 ```bash
178-# Create user systemd service (no root privileges needed!)
179 mkdir -p ~/.config/systemd/user117 mkdir -p ~/.config/systemd/user
180-
181 tee ~/.config/systemd/user/gordon.service > /dev/null <<EOF118 tee ~/.config/systemd/user/gordon.service > /dev/null <<EOF
182 [Unit]119 [Unit]
183-Description=Gordon Container Platform (Rootless)120+Description=Gordon Container Platform
184 After=podman.socket121 After=podman.socket
185-Requires=podman.socket
186 122
187 [Service]123 [Service]
188 Type=simple124 Type=simple
@@ -196,25 +132,17 @@ WorkingDirectory=%h
196 WantedBy=default.target132 WantedBy=default.target
197 EOF133 EOF
198 134
199-# Enable and start the user service
200 systemctl --user daemon-reload135 systemctl --user daemon-reload
201 systemctl --user enable --now gordon136 systemctl --user enable --now gordon
202-
203-# Enable lingering to start service on boot
204 sudo loginctl enable-linger $USER137 sudo loginctl enable-linger $USER
205-
206-# Check service status
207-systemctl --user status gordon
208 ```138 ```
209 139
210-### 8. Deploy Your First App140+### 9. Deploy Your First App
211 ```bash141 ```bash
212-# Now from your local development machine:142+# From your dev machine:
213-# Authenticate with your Gordon registry
214 podman login registry.mydomain.com143 podman login registry.mydomain.com
215-# Use the username/password from your gordon.toml config144+# (Use the credentials from your gordon.toml)
216 145
217-# Deploy your first app (using podman)
218 podman tag myapp:latest registry.mydomain.com/myapp:latest146 podman tag myapp:latest registry.mydomain.com/myapp:latest
219 podman push registry.mydomain.com/myapp:latest147 podman push registry.mydomain.com/myapp:latest
220 # Visit https://app.mydomain.com148 # Visit https://app.mydomain.com
@@ -222,342 +150,111 @@ podman push registry.mydomain.com/myapp:latest
222 150
223 ## Real-World Examples151 ## Real-World Examples
224 152
225-### Deploy a Node.js App (The "Push-to-Deploy" Way)153+### Push-to-Deploy Workflow
226-This is the simplest way to deploy. Your production environment is always in sync with your `latest` tag.
227-
228 ```bash154 ```bash
229-# 1. Build and test locally first155+# Build locally
230 podman build -t myapp .156 podman build -t myapp .
231-podman run -p 3000:3000 myapp # Test it!
232 157
233-# 2. In gordon.toml, point your domain to the 'latest' tag158+# Push latest = instant deploy
234-# "app.mydomain.com" = "myapp:latest"
235-
236-# 3. Tag your image as 'latest' for your registry
237-podman tag myapp registry.mydomain.com/myapp:latest
238-
239-# 4. Push to deploy
240 podman push registry.mydomain.com/myapp:latest159 podman push registry.mydomain.com/myapp:latest
241-
242-# Gordon automatically detects the push and deploys the new 'latest' version.
243 ```160 ```
244 161
245-### Smart Versioning Strategy162+### Version Control & Rollbacks
246-For more control, especially in production, you can use a combination of version tags and the `latest` tag. This makes rollbacks trivial and allows for staging environments.
247-
248 ```bash163 ```bash
249-# 1. Always push a specific version tag first. This creates a history.164+# Push versioned releases
250-podman tag myapp registry.mydomain.com/myapp:v1.0.1
251 podman push registry.mydomain.com/myapp:v1.0.1165 podman push registry.mydomain.com/myapp:v1.0.1
252 166
253-# 2. Test the new version on a staging domain.167+# Instant rollback in gordon.toml:
254-# In gordon.toml:168+# From: "app.mydomain.com" = "myapp:latest"
255-"staging.mydomain.com" = "myapp:v1.0.1"169+# To: "app.mydomain.com" = "myapp:v1.0.0"
256-# Get your production route ready :
257-"app.mydomain.com" = "myapp:latest"
258-
259-# 3. Once tested and confirmed, promote it to production by updating 'latest'.
260-podman tag myapp registry.mydomain.com/myapp:latest
261-podman push registry.mydomain.com/myapp:latest
262-```
263-
264-### Instant Rollback When Things Break
265-```toml
266-# Production broke after the latest push?
267-# Just edit gordon.toml to point to a previously pushed, stable version tag:
268-
269-# From:
270-"app.mydomain.com" = "myapp:latest"
271-
272-# To:
273-"app.mydomain.com" = "myapp:v1.0.0"
274-
275-# Save the file. Gordon redeploys the stable version in seconds. No scripts, no drama.
276 ```170 ```
277 171
278-### Auto-Route Creation for Testing172+### Auto-Route Creation
279-
280-Gordon can automatically create routes from image names that contain valid domain names. This is perfect for testing deployments without manually editing config files.
281-
282-#### Enable Auto-Routes
283 ```toml173 ```toml
284 [auto_route]174 [auto_route]
285-enabled = true # Default: false175+enabled = true
286 ```176 ```
287 177
288-#### How It Works
289-When you push an image with a domain name as the image name, Gordon automatically creates a route:
290-
291 ```bash178 ```bash
292-# Build your app normally179+# Image name becomes route automatically
293-podman build -t myapp .180+podman push registry.mydomain.com/staging.example.com:latest
294-181+# Creates: "staging.example.com" = "staging.example.com:latest"
295-# Tag with domain name as the image name
296-podman tag myapp:latest registry.mydomain.com/myapp.mydomain.dev:latest
297-podman push registry.mydomain.com/myapp.mydomain.dev:latest
298-
299-# Gordon automatically creates:
300-# "myapp.mydomain.dev" = "myapp.mydomain.dev:latest"
301-# The route is added to your config file and deployed instantly!
302 ```182 ```
303 183
304-#### Perfect for Testing Workflows184+## Advanced Configuration
305-```bash
306-# Test different domains for the same app
307-podman build -t myapp .
308-
309-# Push to different test domains using image names
310-podman tag myapp:latest registry.mydomain.com/staging.example.com:latest
311-podman push registry.mydomain.com/staging.example.com:latest
312 185
313-podman tag myapp:latest registry.mydomain.com/demo.example.com:v1.0.0186+### Full Config Structure
314-podman push registry.mydomain.com/demo.example.com:v1.0.0187+```toml
188+# gordon.toml - Auto-generated on first run
189+[server]
190+port = 8080
191+registry_domain = "registry.mydomain.com"
192+runtime = "podman-rootless" # auto, docker, podman, podman-rootless
193+# runtime auto-detects: docker → podman-rootless → podman
194+# Override with env: CONTAINER_HOST=unix:///custom/socket
315 195
316-# Both automatically get their own routes:196+[registry_auth]
317-# staging.example.com -> staging.example.com:latest197+enabled = true
318-# demo.example.com -> demo.example.com:v1.0.0198+username = "admin"
319-```199+password = "your-secure-password"
320 200
321-#### Domain Validation201+[routes]
322-Gordon only creates auto-routes for valid domain names in image names:202+"app.mydomain.com" = "myapp:latest"
323-- ✅ `api.example.com:latest` → Valid subdomain name, route created
324-- ✅ `myapp.dev:v1.0.0` → Valid domain name, route created
325-- ❌ `myapp:latest` → Not a domain, ignored
326-- ❌ `myapp:v1.0.0` → Not a domain, ignored
327-- ❌ `localhost:latest` → Not a valid domain, ignored
328 203
329-#### Route Precedence204+[auto_route]
330-- Existing routes in `[routes]` are never overwritten205+enabled = false # Auto-create routes from image names
331-- Auto-routes are saved to your config file permanently
332-- You can manually edit or remove auto-created routes anytime
333 206
207+[env]
208+dir = "./data/env" # Gordon auto-creates env files here
209+providers = ["pass", "sops"] # Optional secret management
210+# Example env file: ./data/env/app_mydomain_com.env
211+# NODE_ENV=production
212+# DATABASE_URL=postgresql://localhost:5432/prod
213+# API_KEY=${pass:company/api-key} # Unix pass integration
214+# JWT_SECRET=${sops:secrets.yaml:jwt} # SOPS integration
215+
216+[logging]
217+enabled = false
218+level = "info" # trace, debug, info, warn, error
219+dir = "./logs" # Log directory
220+main_log_file = "gordon.log" # Main app log
221+proxy_log_file = "proxy.log" # HTTP traffic log
222+container_log_dir = "containers" # Container logs subdirectory
223+max_size = 100 # MB before rotation
224+max_backups = 3 # Number of old files to keep
225+max_age = 28 # Days to keep old files
226+compress = true # Gzip rotated logs
227+# Creates: gordon.log, proxy.log, containers/*.log
228+```
334 229
335 ## FAQ230 ## FAQ
336 231
337 **Q: How is this different from Traefik/Nginx Proxy Manager?** 232 **Q: How is this different from Traefik/Nginx Proxy Manager?**
338-A: Those are just reverse proxies - you still need to manually start/stop containers. Gordon is a complete deployment platform: built-in registry, automatic deployment on push, and container lifecycle management. It's the difference between a router and a full PaaS.233+A: Those are just reverse proxies. Gordon is a complete deployment platform with built-in registry, automatic deployment, and container lifecycle management.
339 234
340 **Q: Where do builds happen?** 235 **Q: Where do builds happen?**
341-A: On YOUR machine. If it runs locally, it'll run in production. No mysterious build server issues.236+A: On YOUR machine. If it runs locally, it runs in production.
342-
343-**Q: How do I handle broken deployments?**
344-A: Just edit gordon.toml to point to a previous version. Rollback takes seconds, not minutes.
345 237
346-**Q: Do I need CI/CD?** 238+**Q: How do I rollback?**
347-A: Nope! Your computer is the CI/CD. Build, test locally, push when ready. Keep it stupid simple.239+A: Edit gordon.toml to point to a previous version. Takes seconds.
348 240
349 **Q: Do I need Cloudflare?** 241 **Q: Do I need Cloudflare?**
350-A: Yes for SSL certificates. Gordon doesn't handle Let's Encrypt yet, so Cloudflare provides the SSL termination and DDoS protection.242+A: Yes, for SSL certificates and DDoS protection.
351-
352-**Q: Can I run databases?**
353-A: You can in theory, but exposing databases on the internet is risky. Database/app isolation is coming soon for safer database deployments.
354 243
355 **Q: Resource requirements?** 244 **Q: Resource requirements?**
356-A: Runs comfortably on 1GB RAM VPS. Gordon itself uses <15MB of RAM.245+A: Runs on 1GB RAM VPS. Gordon uses <15MB RAM.
357-
358-**Q: What about secrets?**
359-A: Gordon provides built-in environment variable support with secret management via `pass` and `sops`. See the Environment Variables section below.
360-
361-**Q: How does auto-route creation work?**
362-A: When enabled, Gordon watches for pushed images with domain names as image names (like `staging.example.com:latest`) and automatically creates routes. Perfect for testing deployments without manual config edits.
363 246
364-## Philosophy: Build Local, Deploy Simple247+## Philosophy
365-
366-### Why No Build Servers?
367-
368-1. **Your machine already works** - Why replicate your dev environment in CI/CD?
369-2. **Instant feedback** - Build errors show up immediately, not after pushing
370-3. **Perfect reproducibility** - "Works on my machine" becomes a feature, not a bug
371-4. **Zero build queues** - Deploy as fast as your internet can push
372-
373-### The Gordon Way
374 248
375 ```bash249 ```bash
376 # Traditional CI/CD250 # Traditional CI/CD
377 push code → wait for build → hope it works → debug remotely → repeat251 push code → wait for build → hope it works → debug remotely → repeat
378 252
379-# The Gordon Way (with Podman)253+# The Gordon Way
380-podman build locally → test locally → push image → instant deploy254+build locally → test locally → push image → instant deploy
381-```
382-
383-## Architecture
384-
385-- **Event-Driven**: Push events trigger deployments automatically
386-- **Config Hot-Reload**: Edit gordon.toml, changes apply instantly
387-- **Single Binary**: No dependencies except Docker
388-- **Stateless**: Configuration is the only state
389-- **Fast**: Written in Go for minimal overhead
390-
391-## Advanced Usage
392-
393-### Container Runtime Configuration
394-```toml
395-[server]
396-runtime = "auto" # auto, docker, podman, podman-rootless
397-socket_path = "" # optional custom socket path
398-
399-# Examples:
400-# runtime = "docker"
401-# runtime = "podman"
402-# socket_path = "unix:///run/user/1000/podman/podman.sock"
403-```
404-
405-### Environment Override
406-```bash
407-# Override container socket with environment variable
408-export CONTAINER_HOST=unix:///custom/path/container.sock
409-export CONTAINER_HOST=tcp://remote-docker:2376
410-
411-# Works for both Docker and Podman
412-gordon start
413-```
414-
415-### Runtime Auto-Detection
416-Gordon automatically detects available container runtimes in this order:
417-1. **Docker** (`/var/run/docker.sock`)
418-2. **Podman rootless** (`$XDG_RUNTIME_DIR/podman/podman.sock`)
419-3. **Podman root** (`/run/podman/podman.sock`)
420-
421-### Environment Variables & Secret Management
422-
423-Gordon supports per-route environment variables with secure secret management.
424-
425-#### Configuration
426-```toml
427-[env]
428-# Directory where .env files are stored for each route
429-dir = "./data/env" # Default location
430-# Secret providers for secure credential management
431-providers = ["pass", "sops"] # Unix password manager and SOPS
432-```
433-
434-#### Creating Environment Files
435-Gordon automatically creates empty `.env` files for all configured routes when it starts up. The files are named after the domain with special characters replaced by underscores:
436-
437-```bash
438-# Gordon automatically creates these files for you:
439-# For route "app.mydomain.com"
440-./data/env/app_mydomain_com.env
441-
442-# For route "api-v2.mydomain.com"
443-./data/env/api-v2_mydomain_com.env
444-```
445-
446-#### Environment File Format
447-When Gordon creates env files automatically, they include helpful comments and examples.
448-
449-**Security:** Gordon creates the env directory with `0700` permissions and env files with `0600` permissions (owner read/write only) to protect sensitive information.
450-
451-```bash
452-# ./data/env/app_mydomain_com.env (automatically created by Gordon)
453-# Environment variables for route: app.mydomain.com
454-# Image: myapp:latest
455-
456-NODE_ENV=production
457-PORT=3000
458-DATABASE_URL=postgresql://localhost:5432/prod
459-
460-# Secret references (resolved at container start)
461-API_KEY=${pass:company/api-key}
462-JWT_SECRET=${sops:secrets.yaml:app.jwt_secret}
463-```
464-
465-#### Secret Providers
466-
467-**Unix Password Manager (pass)**
468-```bash
469-# Store secrets with pass
470-pass insert company/api-key
471-pass insert company/database-password
472-
473-# Reference in .env files
474-API_KEY=${pass:company/api-key}
475-DB_PASSWORD=${pass:company/database-password}
476-```
477-
478-**SOPS (Secrets OPerationS)**
479-```bash
480-# Create encrypted YAML file
481-sops secrets.yaml
482-
483-# secrets.yaml content:
484-# app:
485-# jwt_secret: "super-secret-key"
486-# database_url: "postgresql://user:pass@db:5432/prod"
487-
488-# Reference in .env files
489-JWT_SECRET=${sops:secrets.yaml:app.jwt_secret}
490-DATABASE_URL=${sops:secrets.yaml:app.database_url}
491-```
492-
493-#### How It Works
494-1. Gordon loads the `.env` file for each route during container deployment
495-2. Secret references (`${provider:path}`) are resolved using the configured providers
496-3. Environment variables are passed to the container at startup
497-4. Missing env files are silently ignored (optional env vars)
498-5. Secret resolution failures cause deployment to fail for security
499-
500-#### Examples
501-
502-**Development Setup**
503-```toml
504-# gordon.toml
505-[env]
506-dir = "./dev-data/env"
507-providers = ["pass"] # Optional for dev secrets
508-
509-[routes]
510-"app.local" = "myapp:latest"
511 ```255 ```
512 256
513-```bash257+Your machine already works. Why replicate it in CI/CD?
514-# ./dev-data/env/app_local.env
515-NODE_ENV=development
516-DEBUG=true
517-DATABASE_URL=postgresql://localhost:5432/dev
518-API_KEY=${pass:dev/api-key} # Optional: use pass for dev secrets
519-```
520-
521-**Production Setup**
522-```toml
523-# gordon.toml
524-[env]
525-dir = "/var/lib/gordon/env"
526-providers = ["pass", "sops"]
527-
528-[routes]
529-"app.company.com" = "company-app:v2.1.0"
530-```
531-
532-```bash
533-# /var/lib/gordon/env/app_company_com.env
534-NODE_ENV=production
535-DATABASE_URL=postgresql://user:${pass:company/db-password}@db:5432/prod
536-API_KEY=${sops:company-secrets.yaml:api.key}
537-JWT_SECRET=${sops:company-secrets.yaml:jwt.secret}
538-```
539-
540-### Port Binding
541-Gordon requires your Dockerfile to explicitly expose ports using the `EXPOSE` instruction. When multiple ports are exposed, Gordon will use the first exposed port for HTTP traffic.
542-
543-Example Dockerfile:
544-```
545-ENTRYPOINT ["/myapp"]
546-EXPOSE 8080
547-```
548-
549-### Registry Operations
550-```bash
551-# List images
552-curl -u admin:password https://registry.mydomain.com/v2/_catalog
553-
554-# List tags
555-curl -u admin:password https://registry.mydomain.com/v2/myapp/tags/list
556-```
557-
558-## Contributing
559-
560-Gordon is open source and we love contributions! Check out our [issues](https://github.com/yourusername/gordon/issues) or submit a PR.
561 258
562 ## License259 ## License
563 260

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