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 @@ | |||
| 5 | 5 | ||
| 6 | 6 | Self-hosted web app deployments. Push to your registry, Gordon does the rest. | |
| 7 | 7 | ||
| 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) | |
| 10 | 10 | - Discuss: [GitHub Discussions](https://github.com/bnema/gordon/discussions) | |
| 11 | 11 | ||
| 12 | 12 | --- | |
| 13 | 13 | ||
| 14 | + | ## What is Gordon? | |
| 15 | + | ||
| 14 | 16 | 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. | |
| 15 | 17 | ||
| 16 | 18 | ```bash | |
| @@ -21,23 +23,24 @@ docker push registry.your-server.com/myapp:latest | |||
| 21 | 23 | ||
| 22 | 24 | Build on your machine, push to deploy. Works from your laptop or CI. | |
| 23 | 25 | ||
| 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 | |
| 29 | 34 | - Single binary, ~15MB RAM | |
| 30 | 35 | ||
| 31 | - | ## Quick Start | |
| 36 | + | ## Documentation | |
| 32 | 37 | ||
| 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)** | |
| 37 | 39 | ||
| 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 | |
| 39 | 42 | ||
| 40 | - | ### Installation | |
| 43 | + | ## Quick Start | |
| 41 | 44 | ||
| 42 | 45 | ```bash | |
| 43 | 46 | # Download and install | |
| @@ -50,333 +53,14 @@ sudo mv gordon /usr/local/bin/ | |||
| 50 | 53 | gordon start | |
| 51 | 54 | ``` | |
| 52 | 55 | ||
| 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. | |
| 372 | 57 | ||
| 373 | 58 | ## Community | |
| 374 | 59 | ||
| 375 | - | Gordon is open source and welcomes contributions: | |
| 376 | 60 | - [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) | |
| 378 | 62 | - [Submit PRs](https://github.com/bnema/gordon/pulls) | |
| 379 | 63 | ||
| 380 | 64 | ## License | |
| 381 | 65 | ||
| 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) | |