# Core Concepts Understanding how Gordon works and why it's designed this way. ## Local-First Development 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? Gordon flips the typical deployment model: 1. **Build locally** where you have computing power 2. **Push the finished image** to your VPS 3. **Gordon deploys automatically** This means faster builds, less VPS resource usage, and a simpler deployment workflow. ## Push-to-Deploy Gordon combines a Docker registry with automatic deployment: ``` ┌──────────────┐ push ┌──────────────┐ │ docker build │ ──────────────>│ Gordon │ │ docker push │ │ Registry │ └──────────────┘ └──────┬───────┘ │ │ event: image.pushed v ┌──────────────┐ │ Deploy │ │ Container │ └──────────────┘ ``` When you push an image, Gordon: 1. Stores the image in its registry 2. Fires an `image.pushed` event 3. Looks up the route for that image 4. Deploys a new container 5. Updates the proxy routing 6. Stops the old container ## Zero-Downtime Updates Gordon ensures your app stays available during updates: 1. **New container starts** while old container is still running 2. **Health check** waits for new container to be ready 3. **Traffic switches** to the new container 4. **Old container stops** after traffic has moved ``` Time ─────────────────────────────────────────────> Old Container: [═══════════════════] ↓ stop New Container: [═════════════════════════> ↑ start ↑ traffic routed ``` ## Deletion and Cleanup Lifecycle Gordon separates configuration removal from destructive data cleanup: - **configured** — an entity exists in Gordon configuration, such as a route in `gordon.toml`. - **active** — an entity has runtime state, such as a running Gordon-managed container. - **preserved** — state intentionally kept after configuration removal, such as volumes or attachment data. - **orphaned** — runtime state no longer referenced by configuration and requiring follow-up cleanup or diagnosis. - **purged** — state explicitly deleted by a destructive cleanup command. - **retained** — preserved state that Gordon reports so operators can decide whether to keep or purge it later. Safe deletion is the default. Removing a route reconciles active route containers so the app is no longer served or restarted by Gordon's monitor. Stateful data such as volumes and attachment data is preserved unless a later purge command explicitly requests deletion. Cleanup reports use additive-only JSON fields so humans and automation can rely on stable keys while Gordon adds more details over time. Current runtime cleanup capabilities are intentionally conservative: - Gordon can identify managed route and attachment containers by labels such as `gordon.managed`, `gordon.route`, and `gordon.attachment`. - Gordon can stop and remove containers. - Existing volume attribution is limited for older volumes; Gordon may need naming heuristics when labels do not identify owner, mount path, or category. - Updating runtime restart policy and relabeling existing volumes are runtime-dependent capabilities and should be treated as best-effort when added. ## Routes Routes map domains to container images: ```toml [routes] "app.example.com" = { image = "myapp:latest" } "api.example.com" = { image = "myapi:v2.1.0" } ``` Route domains must be plain hostnames such as `app.example.com`. Gordon rejects `http://` and `https://` prefixes, `.local` and `.internal` suffixes, localhost names, and IP literals. Legacy `http://...` keys are still read for backward compatibility and rewritten on the next save. When a request comes in for `app.example.com`, Gordon: 1. Looks up the route configuration 2. Finds the running container for `myapp:latest` 3. Proxies the request to that container ### Route Domains Routes use plain hostnames. HTTPS is enabled by default for routes; see the [routes example](./config/routes.md#development-setup) with `dev-app.example.com`. Cloudflare is one deployment option, but Gordon can also terminate TLS with its built-in listener and certificates issued by Gordon's internal CA, with static certificates, or with public ACME certificates when `[tls.acme]` is enabled. ACME supports HTTP-01 and Cloudflare DNS-01 challenge modes. ```toml [routes] "dev-app.example.com" = { image = "internal-app:latest", https = false } ``` ## Network Isolation Each app runs in its own isolated Docker network: ``` ┌────────────────────────────────────────────────┐ │ gordon-app-mydomain-com │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ App │───>│ Postgres │ │ Redis │ │ │ │ :3000 │ │ :5432 │ │ :6379 │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └────────────────────────────────────────────────┘ ``` Benefits: - Containers can't access each other's services - Services are only accessible by name within their network - No port conflicts between apps ## Attachments Attachments are service dependencies for your apps: ```toml [attachments] "app.mydomain.com" = ["postgres:latest", "redis:latest"] ``` Gordon deploys attachments to the same network as your app. Services are accessible by their image name: ```javascript // In your app const db = await connect("postgresql://postgres:5432/mydb"); const cache = await connect("redis://redis:6379"); ``` ## Network Groups Network groups allow multiple apps to share services: ```toml [network_groups] "backend" = ["app.mydomain.com", "api.mydomain.com"] [attachments] "backend" = ["shared-postgres:latest", "shared-redis:latest"] ``` Both `app.mydomain.com` and `api.mydomain.com` can access the shared services. ## Volumes Gordon automatically creates persistent storage from Dockerfile `VOLUME` directives: ```dockerfile FROM postgres:18 VOLUME ["/var/lib/postgresql/data"] ``` Volume behavior: - **auto_create**: Volumes are created automatically (default: true) - **prefix**: Volume names are prefixed with `gordon-` (configurable) - **preserve**: Volumes persist across container updates (default: true) ## Environment Variables Gordon loads environment variables from files based on the domain: ``` ~/.gordon/env/ ├── app_mydomain_com.env ├── api_mydomain_com.env └── admin_mydomain_com.env ``` Domain dots become underscores: `app.mydomain.com` → `app_mydomain_com.env` Variables are merged in order: 1. Dockerfile `ENV` directives (lowest priority) 2. `.env` file values (highest priority) ### Secret Providers Environment files support secret provider syntax: ```bash # From Unix password manager (pass) DATABASE_PASSWORD=${pass:myapp/db-password} # From SOPS encrypted files API_SECRET=${sops:secrets.yaml:api.secret} ``` ## Configuration Hot-Reload Gordon watches its config file and reloads automatically: 1. Edit `~/.config/gordon/gordon.toml` 2. Save the file 3. Gordon reloads hot-reloaded settings such as attachments, network groups, and routes 4. Containers and proxy config sync to match the new configuration Route file edits now reload automatically again. You can also trigger a reload: ```bash gordon reload ``` `gordon reload` sends `SIGUSR1` to the running Gordon process. ## Event System Gordon uses an internal event system for coordination: | Event | Trigger | Action | |-------|---------|--------| | `image.pushed` | Image pushed to registry | Deploy container | | `config.reload` | Config file changed or `gordon reload` sends `SIGUSR1` | Reload config, sync containers, and refresh proxy state | | `manual.deploy` | `gordon deploy ` command | Deploy specific route | | `container.deployed` | Container started | Update proxy cache | ## Backups and Recovery Gordon can run logical PostgreSQL backups for attachment containers. Current design: 1. Detect PostgreSQL attachments attached to a route 2. Execute `pg_dump -Fc` through Gordon runtime operations 3. Store backup artifacts on local filesystem storage 4. Expose backup actions through admin API and CLI This is intentionally scoped for operational safety and predictable behavior. Future extensions can add physical backups, PITR, and remote object storage adapters. For configuration details and usage examples, see the [Backups Configuration guide](./config/backups.md), [Backup CLI reference](./cli/backup.md), and [Configuration Reference](./config/reference.md). ## Container Labels Gordon uses labels to track managed containers: | Label | Purpose | |-------|---------| | `gordon.managed=true` | Identifies Gordon-managed containers | | `gordon.domain` | Domain this container serves | | `gordon.image` | Image name and tag | | `gordon.route` | Route this container handles | | `gordon.attachment=true` | Container is an attachment service | | `gordon.attached-to` | Which route this attachment serves | ## Proxy Port Selection When a container exposes multiple ports, Gordon needs to know which one serves HTTP: ```dockerfile FROM gitea/gitea:latest LABEL gordon.proxy.port=3000 # Route HTTP to port 3000 EXPOSE 22 # SSH EXPOSE 3000 # HTTP ``` Without the label, Gordon uses the first exposed port. ## Related - [Configuration Reference](./config/index.md) - [Docker Labels Reference](./reference/docker-labels.md) - [Environment Variables](./config/env.md)