v2.31.0-dev23 58a92f3

getting-started.md RawHistoryFolder

Getting Started

Deploy your first app with Gordon in under 5 minutes.

Prerequisites

  • A Linux VPS with Docker or Podman installed
  • A domain pointing to your VPS (DNS A record)
  • A public TCP edge address for Gordon (for example external :443, mapped to your chosen entrypoints.edge.address)
  • pass (password manager) with GPG key initialized

1. Install Gordon

curl -fsSL https://gordon.bnema.dev/install | bash

This script automatically detects your OS and architecture, downloads the appropriate binary, and installs it to /usr/local/bin.

2. Initialize Configuration

# First run creates the default config
gordon serve
# Press Ctrl+C to stop

Config is created at ~/.config/gordon/gordon.toml.

3. Set Up Authentication

Gordon requires a token_secret for JWT authentication. You can store it in pass or provide it via the GORDON_AUTH_TOKEN_SECRET environment variable. We recommend using pass to store secrets securely.

Local development? If you just want to try Gordon locally, you can disable auth temporarily:

[auth]
enabled = false

Skip to Step 4. For production, continue below.

Initialize pass (if not already done):

# Generate a GPG key if you don't have one
gpg --gen-key

# Initialize pass with your GPG key ID
pass init <your-gpg-key-id>

Create the JWT token secret:

# Generate and store a random 32-character secret
openssl rand -base64 32 | pass insert -m gordon/auth/token_secret

Or set it via environment variable:

export GORDON_AUTH_TOKEN_SECRET="your-32-character-secret-here"

Update your config (~/.config/gordon/gordon.toml):

[auth]
enabled = true
secrets_backend = "pass"
token_secret = "gordon/auth/token_secret"

4. Configure Your Gordon Domain

Edit ~/.config/gordon/gordon.toml:

[server]
registry_port = 5000                     # Registry port
gordon_domain = "gordon.mydomain.com"    # Registry + Admin API domain

[entrypoints.edge]
address = ":443"                         # Public smart TCP edge (choose your bind/mapping)
protocol = "smart_tcp"

[routes]
"app.mydomain.com" = "myapp:latest"      # Domain → Image mapping

5. Set Up DNS (Including Wildcard)

gordon_domain is the single domain used by both the registry and admin API.

In Cloudflare (or your DNS provider), create:

Type Name Content Proxy
A gordon YOUR_SERVER_IP Yes
A/CNAME * YOUR_SERVER_IP or gordon.mydomain.com Yes

Why wildcard (*)?

  • It automatically covers app routes like app.mydomain.com, api.mydomain.com, demo.mydomain.com, etc.
  • You can add new domains in [routes] without creating DNS records one by one.

If your DNS provider supports wildcard CNAME flattening (Cloudflare does), * -> gordon.mydomain.com is usually the cleanest option.

Important for Cloudflare or other proxied setups: when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to server.proxy_allowed_ips or proxied HTTP traffic can get 403 Forbidden. See Installation.

6. Start Gordon as a Service

# Create systemd user service
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/gordon.service <<EOF
[Unit]
Description=Gordon Container Platform

[Service]
Type=simple
Restart=always
ExecStart=/usr/local/bin/gordon serve

[Install]
WantedBy=default.target
EOF

# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now gordon
sudo loginctl enable-linger $USER

7. Generate a Deploy Token

Create a token for remote CLI deploys (skip if auth is disabled):

gordon auth token generate --subject deploy --scopes push,pull --expiry 0

--expiry 0 creates a non-expiring token. Prefer a finite expiry and a rotation policy unless you explicitly need a long-lived deploy token.

Save this token securely -- you will use it to authenticate with your Gordon server from CI/CD pipelines and remote CLI sessions.

8. Deploy Your First App

On your local machine:

# Save and select your Gordon remote (one-time)
gordon remotes add prod https://gordon.mydomain.com --token <your-token>
gordon remotes use prod

# Recommended first-time setup
gordon bootstrap app.example.com myapp:latest --attachment postgres:18 --env APP_ENV=production

# Then build, push, and deploy
gordon push myapp:latest --domain app.example.com --build --no-confirm

What this command does:

  • gordon bootstrap creates or updates the route, applies requested attachments, and stores environment variables.
  • This is the recommended path for first deploys because it does not require the route to exist ahead of time.
  • Run gordon push separately after bootstrap to build, upload, and deploy the image.

If the route already exists and you only need to push a new image version, use:

gordon push myapp --build --no-confirm

gordon push still requires the route to already exist so it can resolve the deploy target.

Your app is now live at https://app.mydomain.com!

9. Update Your App

Push a new image to deploy with zero downtime:

# Make changes, then build + push + deploy
gordon push myapp --build --no-confirm

Gordon automatically:

  1. Starts the new container
  2. Waits for it to be ready
  3. Routes traffic to the new container
  4. Stops the old container

Next Steps

source
1# Getting Started
2
3Deploy 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- A public TCP edge address for Gordon (for example external `:443`, mapped to your chosen `entrypoints.edge.address`)
10- [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized
11
12## 1. Install Gordon
13
14```bash
15curl -fsSL https://gordon.bnema.dev/install | bash
16```
17
18This script automatically detects your OS and architecture, downloads the appropriate binary, and installs it to `/usr/local/bin`.
19
20## 2. Initialize Configuration
21
22```bash
23# First run creates the default config
24gordon serve
25# Press Ctrl+C to stop
26```
27
28Config is created at `~/.config/gordon/gordon.toml`.
29
30## 3. Set Up Authentication
31
32Gordon requires a `token_secret` for JWT authentication. You can store it in `pass` or provide it via the `GORDON_AUTH_TOKEN_SECRET` environment variable. We recommend using `pass` to store secrets securely.
33
34> **Local development?** If you just want to try Gordon locally, you can disable auth temporarily:
35> ```toml
36> [auth]
37> enabled = false
38> ```
39> Skip to [Step 4](#4-configure-your-gordon-domain). For production, continue below.
40
41**Initialize pass (if not already done):**
42```bash
43# Generate a GPG key if you don't have one
44gpg --gen-key
45
46# Initialize pass with your GPG key ID
47pass init <your-gpg-key-id>
48```
49
50**Create the JWT token secret:**
51```bash
52# Generate and store a random 32-character secret
53openssl rand -base64 32 | pass insert -m gordon/auth/token_secret
54```
55
56**Or set it via environment variable:**
57
58```bash
59export GORDON_AUTH_TOKEN_SECRET="your-32-character-secret-here"
60```
61
62**Update your config** (`~/.config/gordon/gordon.toml`):
63```toml
64[auth]
65enabled = true
66secrets_backend = "pass"
67token_secret = "gordon/auth/token_secret"
68```
69
70## 4. Configure Your Gordon Domain
71
72Edit `~/.config/gordon/gordon.toml`:
73
74```toml
75[server]
76registry_port = 5000                     # Registry port
77gordon_domain = "gordon.mydomain.com"    # Registry + Admin API domain
78
79[entrypoints.edge]
80address = ":443"                         # Public smart TCP edge (choose your bind/mapping)
81protocol = "smart_tcp"
82
83[routes]
84"app.mydomain.com" = "myapp:latest"      # Domain → Image mapping
85```
86
87## 5. Set Up DNS (Including Wildcard)
88
89`gordon_domain` is the single domain used by both the registry and admin API.
90
91In Cloudflare (or your DNS provider), create:
92
93| Type | Name | Content | Proxy |
94|------|------|---------|-------|
95| A | `gordon` | `YOUR_SERVER_IP` | Yes |
96| A/CNAME | `*` | `YOUR_SERVER_IP` or `gordon.mydomain.com` | Yes |
97
98Why wildcard (`*`)?
99- It automatically covers app routes like `app.mydomain.com`, `api.mydomain.com`, `demo.mydomain.com`, etc.
100- You can add new domains in `[routes]` without creating DNS records one by one.
101
102If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option.
103
104> **Important for Cloudflare or other proxied setups:** when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic can get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist).
105
106## 6. Start Gordon as a Service
107
108```bash
109# Create systemd user service
110mkdir -p ~/.config/systemd/user
111cat > ~/.config/systemd/user/gordon.service <<EOF
112[Unit]
113Description=Gordon Container Platform
114
115[Service]
116Type=simple
117Restart=always
118ExecStart=/usr/local/bin/gordon serve
119
120[Install]
121WantedBy=default.target
122EOF
123
124# Enable and start
125systemctl --user daemon-reload
126systemctl --user enable --now gordon
127sudo loginctl enable-linger $USER
128```
129
130## 7. Generate a Deploy Token
131
132Create a token for remote CLI deploys (skip if auth is disabled):
133
134```bash
135gordon auth token generate --subject deploy --scopes push,pull --expiry 0
136```
137
138`--expiry 0` creates a non-expiring token. Prefer a finite expiry and a rotation policy unless you explicitly need a long-lived deploy token.
139
140Save this token securely -- you will use it to authenticate with your Gordon server from CI/CD pipelines and remote CLI sessions.
141
142## 8. Deploy Your First App
143
144On your local machine:
145
146```bash
147# Save and select your Gordon remote (one-time)
148gordon remotes add prod https://gordon.mydomain.com --token <your-token>
149gordon remotes use prod
150
151# Recommended first-time setup
152gordon bootstrap app.example.com myapp:latest --attachment postgres:18 --env APP_ENV=production
153
154# Then build, push, and deploy
155gordon push myapp:latest --domain app.example.com --build --no-confirm
156```
157
158What this command does:
159
160- `gordon bootstrap` creates or updates the route, applies requested attachments,
161  and stores environment variables.
162- This is the recommended path for first deploys because it does not require the
163  route to exist ahead of time.
164- Run `gordon push` separately after bootstrap to build, upload, and deploy the image.
165
166If the route already exists and you only need to push a new image version, use:
167
168```bash
169gordon push myapp --build --no-confirm
170```
171
172`gordon push` still requires the route to already exist so it can resolve the
173deploy target.
174
175Your app is now live at `https://app.mydomain.com`!
176
177## 9. Update Your App
178
179Push a new image to deploy with zero downtime:
180
181```bash
182# Make changes, then build + push + deploy
183gordon push myapp --build --no-confirm
184```
185
186Gordon automatically:
1871. Starts the new container
1882. Waits for it to be ready
1893. Routes traffic to the new container
1904. Stops the old container
191
192## Next Steps
193
194- [Installation Guide](./installation.md) - Production setup with firewall and rootless containers
195- [Configuration Reference](./config/index.md) - All configuration options
196- [Authentication](./config/auth.md) - Secure your registry
197- [Environment Variables](./config/env.md) - Configure per-app settings
198
199## Related
200
201- [First Deploy Tutorial](/wiki/tutorials/first-deploy.md)
202- [Podman Rootless Setup](/wiki/guides/podman-rootless.md)

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