v3.0.0-alpha.2 7289826

README.md RawHistoryFolder

Gordon

[License: GPL-3.0]

Self-hosted container deployment. Push an image, declare an app, deploy it.


What is Gordon?

Gordon is a private container registry, an app runtime, and a reverse proxy for your VPS.

The flow is explicit: build and push an image, declare the app in a standalone TOML file, apply it, then deploy. Push transfers OCI content only — it never deploys.

Quick Start

# Install the latest stable release to ~/.local/bin
curl -fsSL https://bnema.dev/gordon/install | sh

# Start the server (restart your shell first if the installer updated PATH)
gordon serve

The installer uses ~/.local/bin without sudo and can add the effective install directory to Fish, Bash, or Zsh PATH configuration. Set GORDON_UPDATE_PATH=1 to update PATH without prompting or GORDON_UPDATE_PATH=0 to leave configuration unchanged. Override the destination with an absolute path such as GORDON_INSTALL_DIR="$HOME/bin", GORDON_INSTALL_DIR="$HOME/.local/bin", or GORDON_INSTALL_DIR=/usr/local/bin.

To build the current next branch commit locally, use GORDON_CHANNEL=next. This is an unverified development source build, not a checksum-verified release, and requires a compatible Go toolchain. See the installation guide.

Config is created at ~/.config/gordon/gordon.toml. See the Getting Started guide for full setup.

Deploy with the CLI

Build locally, push to your Gordon server, declare the app, deploy:

# Push the image (OCI transfer only, never deploys)
gordon images push myapp --build --remote prod

# Declare the app (blog.toml references the pushed tag)
gordon apps apply --file blog.toml --remote prod

# Activate it
gordon apps deploy blog --remote prod

# Check status
gordon daemon status
gordon apps status blog --remote prod

Minimal app file (blog.toml):

name = "blog"

[services.web]
image = "gordon.mydomain.com/myapp:v1.2.0"

[[services.web.http]]
host = "blog.mydomain.com"
port = 3000

Manage lifecycle and secrets — all from the command line:

gordon apps restart blog --remote prod   # Restart from pinned digests
gordon apps stop blog --remote prod      # Stop, preserve all data
gordon apps secrets set blog --service web DATABASE_URL=... --remote prod

Deploy from CI/CD

Push to Gordon's registry from any CI pipeline, then apply and deploy with the CLI. Push never triggers a deploy by itself.

GitHub Actions

- uses: bnema/gordon/.github/actions/deploy@main
  with:
    registry: registry.mydomain.com
    username: ${{ secrets.GORDON_USERNAME }}
    password: ${{ secrets.GORDON_TOKEN }}

See the Deploy Action README for multi-platform builds, monorepo support, and all available options.

Docker CLI + Gordon CLI

docker login gordon.mydomain.com
docker build -t gordon.mydomain.com/myapp:v1.0.0 .
docker push gordon.mydomain.com/myapp:v1.0.0
# Then, with the Gordon binary:
gordon apps apply --file blog.toml --remote prod
gordon apps deploy blog --remote prod

CLI Commands

Server

Command Description
gordon serve Start the Gordon server
gordon daemon status Show server and app fleet status
gordon daemon logs Show Gordon process logs
gordon daemon reload Reload installation configuration
gordon daemon config show Display installation configuration

Applications

Command Description
gordon apps apply --file FILE Validate and persist an app manifest
gordon apps deploy APP Activate an app revision
gordon apps list List applications
gordon apps show APP Show desired and active state
gordon apps status APP Show effective vs observed state
gordon apps logs APP Show logs for an app service
gordon apps restart APP Restart from pinned digests
gordon apps stop APP Stop, preserve all data
gordon apps start APP Start a stopped app
gordon apps remove APP Remove workloads (volumes/secrets retained)

Images & Registry

Command Description
gordon images push [image] Tag and push an image (never deploys)
gordon images list List runtime and registry images
gordon images prune Clean up dangling images and old tags
gordon images tags <repo> List registry tags for a repository

Secrets

Command Description
gordon apps secrets list APP List app secret names
gordon apps secrets set APP --service SVC KEY=VAL Set app secret values

Remotes & Auth

Command Description
gordon remotes list List remote Gordon endpoints
gordon remotes add <name> <url> Add a remote
gordon remotes use <name> Set the active remote
gordon auth login Authenticate to a remote
gordon auth token generate Generate a JWT token

Features

  • Private Docker/Podman registry on your VPS
  • Declarative apps: one TOML file per app, explicit apply then deploy
  • Domain-to-container routing through a smart TCP edge reverse proxy
  • Sequential service replacement (withdraw traffic, replace one generation, verify readiness, republish)
  • Remote CLI management (daemon is the sole writer)
  • Declarative per-service volumes, retained across lifecycle operations
  • Per-service secrets in pass, app-wide public env in the manifest
  • Per-app private networks plus opt-in shared networks
  • Single binary

[!NOTE] Gordon exposes public traffic through entrypoints such as [entrypoints.edge] with protocol = "smart_tcp". It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. Use Gordon's ownership-aware volume commands; runtime commands such as docker volume prune bypass Gordon's retention checks.

Documentation

Full documentation at bnema.dev/gordon

  • Docs — Installation, configuration, CLI reference
  • Wiki — Tutorials, guides, and examples

Community

License

GPL-3.0 — Use freely, contribute back.

source
1# Gordon
2
3[![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
4
5Self-hosted container deployment. Push an image, declare an app, deploy it.
6
7- Website: https://bnema.dev/gordon
8- Documentation: [Docs](https://bnema.dev/gordon/docs) | [Wiki](https://bnema.dev/gordon/wiki)
9- Discuss: [GitHub Discussions](https://github.com/bnema/gordon/discussions)
10
11---
12
13## What is Gordon?
14
15Gordon is a private container registry, an app runtime, and a reverse proxy for your VPS.
16
17The flow is explicit: build and push an image, declare the app in a standalone TOML file, apply it, then deploy. Push transfers OCI content only — it never deploys.
18
19## Quick Start
20
21```bash
22# Install the latest stable release to ~/.local/bin
23curl -fsSL https://bnema.dev/gordon/install | sh
24
25# Start the server (restart your shell first if the installer updated PATH)
26gordon serve
27```
28
29The installer uses `~/.local/bin` without `sudo` and can add the effective install directory to Fish, Bash, or Zsh PATH configuration. Set `GORDON_UPDATE_PATH=1` to update PATH without prompting or `GORDON_UPDATE_PATH=0` to leave configuration unchanged. Override the destination with an absolute path such as `GORDON_INSTALL_DIR="$HOME/bin"`, `GORDON_INSTALL_DIR="$HOME/.local/bin"`, or `GORDON_INSTALL_DIR=/usr/local/bin`.
30
31To build the current `next` branch commit locally, use `GORDON_CHANNEL=next`. This is an unverified development source build, not a checksum-verified release, and requires a compatible Go toolchain. See the [installation guide](https://bnema.dev/gordon/docs/installation#choosing-an-install-channel).
32
33Config is created at `~/.config/gordon/gordon.toml`. See the [Getting Started guide](https://bnema.dev/gordon/docs/getting-started) for full setup.
34
35## Deploy with the CLI
36
37Build locally, push to your Gordon server, declare the app, deploy:
38
39```bash
40# Push the image (OCI transfer only, never deploys)
41gordon images push myapp --build --remote prod
42
43# Declare the app (blog.toml references the pushed tag)
44gordon apps apply --file blog.toml --remote prod
45
46# Activate it
47gordon apps deploy blog --remote prod
48
49# Check status
50gordon daemon status
51gordon apps status blog --remote prod
52```
53
54Minimal app file (`blog.toml`):
55
56```toml
57name = "blog"
58
59[services.web]
60image = "gordon.mydomain.com/myapp:v1.2.0"
61
62[[services.web.http]]
63host = "blog.mydomain.com"
64port = 3000
65```
66
67Manage lifecycle and secrets — all from the command line:
68
69```bash
70gordon apps restart blog --remote prod   # Restart from pinned digests
71gordon apps stop blog --remote prod      # Stop, preserve all data
72gordon apps secrets set blog --service web DATABASE_URL=... --remote prod
73```
74
75## Deploy from CI/CD
76
77Push to Gordon's registry from any CI pipeline, then apply and deploy with the CLI. Push never triggers a deploy by itself.
78
79### GitHub Actions
80
81```yaml
82- uses: bnema/gordon/.github/actions/deploy@main
83  with:
84    registry: registry.mydomain.com
85    username: ${{ secrets.GORDON_USERNAME }}
86    password: ${{ secrets.GORDON_TOKEN }}
87```
88
89See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platform builds, monorepo support, and all available options.
90
91### Docker CLI + Gordon CLI
92
93```bash
94docker login gordon.mydomain.com
95docker build -t gordon.mydomain.com/myapp:v1.0.0 .
96docker push gordon.mydomain.com/myapp:v1.0.0
97# Then, with the Gordon binary:
98gordon apps apply --file blog.toml --remote prod
99gordon apps deploy blog --remote prod
100```
101
102## CLI Commands
103
104### Server
105
106| Command | Description |
107|---------|-------------|
108| `gordon serve` | Start the Gordon server |
109| `gordon daemon status` | Show server and app fleet status |
110| `gordon daemon logs` | Show Gordon process logs |
111| `gordon daemon reload` | Reload installation configuration |
112| `gordon daemon config show` | Display installation configuration |
113
114### Applications
115
116| Command | Description |
117|---------|-------------|
118| `gordon apps apply --file FILE` | Validate and persist an app manifest |
119| `gordon apps deploy APP` | Activate an app revision |
120| `gordon apps list` | List applications |
121| `gordon apps show APP` | Show desired and active state |
122| `gordon apps status APP` | Show effective vs observed state |
123| `gordon apps logs APP` | Show logs for an app service |
124| `gordon apps restart APP` | Restart from pinned digests |
125| `gordon apps stop APP` | Stop, preserve all data |
126| `gordon apps start APP` | Start a stopped app |
127| `gordon apps remove APP` | Remove workloads (volumes/secrets retained) |
128
129### Images & Registry
130
131| Command | Description |
132|---------|-------------|
133| `gordon images push [image]` | Tag and push an image (never deploys) |
134| `gordon images list` | List runtime and registry images |
135| `gordon images prune` | Clean up dangling images and old tags |
136| `gordon images tags <repo>` | List registry tags for a repository |
137
138### Secrets
139
140| Command | Description |
141|---------|-------------|
142| `gordon apps secrets list APP` | List app secret names |
143| `gordon apps secrets set APP --service SVC KEY=VAL` | Set app secret values |
144
145### Remotes & Auth
146
147| Command | Description |
148|---------|-------------|
149| `gordon remotes list` | List remote Gordon endpoints |
150| `gordon remotes add <name> <url>` | Add a remote |
151| `gordon remotes use <name>` | Set the active remote |
152| `gordon auth login` | Authenticate to a remote |
153| `gordon auth token generate` | Generate a JWT token |
154
155## Features
156
157- Private Docker/Podman registry on your VPS
158- Declarative apps: one TOML file per app, explicit apply then deploy
159- Domain-to-container routing through a smart TCP edge reverse proxy
160- Sequential service replacement (withdraw traffic, replace one generation, verify readiness, republish)
161- Remote CLI management (daemon is the sole writer)
162- Declarative per-service volumes, retained across lifecycle operations
163- Per-service secrets in pass, app-wide public env in the manifest
164- Per-app private networks plus opt-in shared networks
165- Single binary
166
167> [!NOTE]
168> Gordon exposes public traffic through entrypoints such as `[entrypoints.edge]` with `protocol = "smart_tcp"`. It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. Use Gordon's ownership-aware volume commands; runtime commands such as `docker volume prune` bypass Gordon's retention checks.
169
170## Documentation
171
172Full documentation at **[bnema.dev/gordon](https://bnema.dev/gordon)**
173
174- [Docs](https://bnema.dev/gordon/docs) — Installation, configuration, CLI reference
175- [Wiki](https://bnema.dev/gordon/wiki) — Tutorials, guides, and examples
176
177## Community
178
179- [Report bugs](https://github.com/bnema/gordon/issues)
180- [Discussions](https://github.com/bnema/gordon/discussions)
181- [Submit PRs](https://github.com/bnema/gordon/pulls)
182
183## License
184
185GPL-3.0 — Use freely, contribute back.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close