v2.15.0 cf76057

attachments.md RawHistoryFolder

Attachments Configuration

Attach service dependencies (databases, caches, queues) to your applications.

Requirements

Network isolation must be enabled for attachments to work properly:

[network_isolation]
enabled = true

Without network isolation, containers run on Docker's default bridge network which does not provide DNS resolution. Your application won't be able to reach attachments by hostname (e.g., postgres:5432).

Configuration

[attachments]
"app.mydomain.com" = ["postgres:latest", "redis:latest"]
"api.mydomain.com" = ["postgres:latest"]

Syntax

[attachments]
"<domain-or-group>" = ["<image>:<tag>", "<image>:<tag>"]
Component Description
domain-or-group Route domain or network group name
image:tag Service images to deploy alongside the app

How Attachments Work

When you define attachments:

  1. Gordon deploys attachment containers to the same network as your app
  2. Services are accessible by their image name (before the colon)
  3. Attachments start before your main application
  4. Attachments persist across app updates
┌───────────────────────────────────────────────────┐
│ Network: gordon-app-mydomain-com                  │
│                                                   │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │   App    │───>│ postgres │    │  redis   │     │
│  │  :3000   │    │  :5432   │    │  :6379   │     │
│  └──────────┘    └──────────┘    └──────────┘     │
│                                                   │
└───────────────────────────────────────────────────┘

Service Discovery

Attachments are accessible by their image name within the network:

[attachments]
"app.mydomain.com" = ["postgres:latest", "redis:latest"]

In your application:

// Connect using simple hostnames
const db = await pg.connect("postgresql://postgres:5432/mydb");
const cache = await redis.connect("redis://redis:6379");

Persistent Storage

Use Dockerfile VOLUME directives for persistent data:

# postgres.Dockerfile
FROM postgres:18
VOLUME ["/var/lib/postgresql/data"]
ENV POSTGRES_DB=myapp
ENV POSTGRES_USER=app
# Password is injected via attachment secrets — do NOT hardcode here

Configure credentials via attachment secrets instead of hardcoding them:

# Set database password securely
gordon secrets set app.mydomain.com --attachment postgres POSTGRES_PASSWORD=secret

# Verify
gordon secrets list app.mydomain.com

This works with all secrets backends (pass, sops, unsafe). See Secrets Configuration for backend details.

Build and push to Gordon:

docker build -f postgres.Dockerfile -t my-postgres:latest .
docker push registry.mydomain.com/my-postgres:latest

Use in attachments:

[attachments]
"app.mydomain.com" = ["my-postgres:latest"]

Attachment Secrets

Inject environment variables into attachment containers using the --attachment flag on secrets commands:

# Set secrets for the postgres attachment
gordon secrets set app.mydomain.com --attachment postgres POSTGRES_USER=admin POSTGRES_PASSWORD=secret

# Set secrets for the redis attachment
gordon secrets set app.mydomain.com --attachment redis REDIS_PASSWORD=cache-secret

# View all secrets (domain + attachments) in a tree view
gordon secrets list app.mydomain.com

# Remove an attachment secret
gordon secrets remove app.mydomain.com --attachment postgres POSTGRES_PASSWORD

The --attachment flag takes the service name (the image name before the colon in your attachments config). For example, if your config has "app.mydomain.com" = ["postgres:18", "redis:7-alpine"], the service names are postgres and redis.

Storage depends on your secrets backend:

  • pass: gordon/env/attachments/<container-name>/<KEY>
  • sops/unsafe: gordon-<container-name>.env files

See Secrets Configuration and Secrets Commands for details.

Shared Attachments with Network Groups

Share services between multiple apps using network groups:

[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.

Per-App vs Shared Attachments

Per-App Attachments

Each app gets its own isolated service instances:

[attachments]
"app.mydomain.com" = ["postgres:latest"]   # App's own postgres
"api.mydomain.com" = ["postgres:latest"]   # API's own postgres

Shared Attachments

Multiple apps share the same service instances:

[network_groups]
"backend" = ["app.mydomain.com", "api.mydomain.com"]

[attachments]
"backend" = ["postgres:latest", "redis:latest"]  # Shared by both

Mixed Approach

[network_groups]
"backend" = ["app.mydomain.com", "api.mydomain.com"]

[attachments]
"backend" = ["redis:latest"]                   # Shared cache
"app.mydomain.com" = ["postgres:latest"]       # App's own database
"api.mydomain.com" = ["postgres:latest"]       # API's own database

Examples

Web App with Database

[routes]
"app.mydomain.com" = "myapp:latest"

[attachments]
"app.mydomain.com" = ["postgres:18", "redis:7-alpine"]

Microservices with Shared Queue

[routes]
"orders.mydomain.com" = "orders-service:latest"
"inventory.mydomain.com" = "inventory-service:latest"
"notifications.mydomain.com" = "notifications-service:latest"

[network_groups]
"services" = ["orders.mydomain.com", "inventory.mydomain.com", "notifications.mydomain.com"]

[attachments]
"services" = ["rabbitmq:3-management"]
"orders.mydomain.com" = ["orders-db:latest"]
"inventory.mydomain.com" = ["inventory-db:latest"]

Custom Service Images

Build custom service images with your configuration:

# my-postgres.Dockerfile
FROM postgres:18
VOLUME ["/var/lib/postgresql/data"]
ENV POSTGRES_DB=production
ENV POSTGRES_USER=appuser
# Password should come from env file
# my-redis.Dockerfile
FROM redis:7-alpine
VOLUME ["/data"]
CMD ["redis-server", "--appendonly", "yes"]
[attachments]
"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]

Container Naming

Attachment containers are named:

  • gordon-<domain>-<image-name>
  • Example: gordon-app-mydomain-com-postgres

Labels

Gordon adds labels to attachment containers:

Label Value
gordon.managed true
gordon.attachment true
gordon.attached-to Domain or group name

CLI Management

Manage attachments via the CLI without editing configuration files.

List Attachments

# List all attachments
gordon attachments list

# List attachments for a specific domain or network group
gordon attachments list app.mydomain.com
gordon attachments list backend

# Remote mode
gordon attachments list --remote https://gordon.mydomain.com --token $TOKEN

Add Attachments

# Add attachment to a domain
gordon attachments add app.mydomain.com postgres:18

# Add attachment to a network group
gordon attachments add backend redis:7-alpine

# Remote mode
gordon attachments add app.mydomain.com postgres:18 --remote https://gordon.mydomain.com --token $TOKEN

Remove Attachments

# Remove attachment from a domain
gordon attachments remove app.mydomain.com postgres:18

# Remove from network group
gordon attachments remove backend redis:7-alpine

# Remote mode
gordon attachments remove app.mydomain.com postgres:18 --remote https://gordon.mydomain.com --token $TOKEN

Alias

The gordon attach command is an alias for gordon attachments:

gordon attach list
gordon attach add app.mydomain.com postgres:18
gordon attach remove app.mydomain.com postgres:18
source
1# Attachments Configuration
2
3Attach service dependencies (databases, caches, queues) to your applications.
4
5## Requirements
6
7**Network isolation must be enabled** for attachments to work properly:
8
9```toml
10[network_isolation]
11enabled = true
12```
13
14Without network isolation, containers run on Docker's default bridge network which **does not provide DNS resolution**. Your application won't be able to reach attachments by hostname (e.g., `postgres:5432`).
15
16## Configuration
17
18```toml
19[attachments]
20"app.mydomain.com" = ["postgres:latest", "redis:latest"]
21"api.mydomain.com" = ["postgres:latest"]
22```
23
24## Syntax
25
26```toml
27[attachments]
28"<domain-or-group>" = ["<image>:<tag>", "<image>:<tag>"]
29```
30
31| Component | Description |
32|-----------|-------------|
33| `domain-or-group` | Route domain or network group name |
34| `image:tag` | Service images to deploy alongside the app |
35
36## How Attachments Work
37
38When you define attachments:
39
401. Gordon deploys attachment containers to the same network as your app
412. Services are accessible by their image name (before the colon)
423. Attachments start before your main application
434. Attachments persist across app updates
44
45```
46┌───────────────────────────────────────────────────┐
47│ Network: gordon-app-mydomain-com                  │
48│                                                   │
49│  ┌──────────┐    ┌──────────┐    ┌──────────┐     │
50│  │   App    │───>│ postgres │    │  redis   │     │
51│  │  :3000   │    │  :5432   │    │  :6379   │     │
52│  └──────────┘    └──────────┘    └──────────┘     │
53│                                                   │
54└───────────────────────────────────────────────────┘
55```
56
57## Service Discovery
58
59Attachments are accessible by their image name within the network:
60
61```toml
62[attachments]
63"app.mydomain.com" = ["postgres:latest", "redis:latest"]
64```
65
66In your application:
67```javascript
68// Connect using simple hostnames
69const db = await pg.connect("postgresql://postgres:5432/mydb");
70const cache = await redis.connect("redis://redis:6379");
71```
72
73## Persistent Storage
74
75Use Dockerfile VOLUME directives for persistent data:
76
77```dockerfile
78# postgres.Dockerfile
79FROM postgres:18
80VOLUME ["/var/lib/postgresql/data"]
81ENV POSTGRES_DB=myapp
82ENV POSTGRES_USER=app
83# Password is injected via attachment secrets — do NOT hardcode here
84```
85
86Configure credentials via attachment secrets instead of hardcoding them:
87
88```bash
89# Set database password securely
90gordon secrets set app.mydomain.com --attachment postgres POSTGRES_PASSWORD=secret
91
92# Verify
93gordon secrets list app.mydomain.com
94```
95
96This works with all secrets backends (pass, sops, unsafe). See [Secrets Configuration](./secrets.md) for backend details.
97
98Build and push to Gordon:
99```bash
100docker build -f postgres.Dockerfile -t my-postgres:latest .
101docker push registry.mydomain.com/my-postgres:latest
102```
103
104Use in attachments:
105```toml
106[attachments]
107"app.mydomain.com" = ["my-postgres:latest"]
108```
109
110## Attachment Secrets
111
112Inject environment variables into attachment containers using the `--attachment` flag on secrets commands:
113
114```bash
115# Set secrets for the postgres attachment
116gordon secrets set app.mydomain.com --attachment postgres POSTGRES_USER=admin POSTGRES_PASSWORD=secret
117
118# Set secrets for the redis attachment
119gordon secrets set app.mydomain.com --attachment redis REDIS_PASSWORD=cache-secret
120
121# View all secrets (domain + attachments) in a tree view
122gordon secrets list app.mydomain.com
123
124# Remove an attachment secret
125gordon secrets remove app.mydomain.com --attachment postgres POSTGRES_PASSWORD
126```
127
128The `--attachment` flag takes the service name (the image name before the colon in your attachments config). For example, if your config has `"app.mydomain.com" = ["postgres:18", "redis:7-alpine"]`, the service names are `postgres` and `redis`.
129
130Storage depends on your secrets backend:
131- **pass**: `gordon/env/attachments/<container-name>/<KEY>`
132- **sops/unsafe**: `gordon-<container-name>.env` files
133
134See [Secrets Configuration](./secrets.md) and [Secrets Commands](../cli/secrets.md) for details.
135
136## Shared Attachments with Network Groups
137
138Share services between multiple apps using network groups:
139
140```toml
141[network_groups]
142"backend" = ["app.mydomain.com", "api.mydomain.com"]
143
144[attachments]
145"backend" = ["shared-postgres:latest", "shared-redis:latest"]
146```
147
148Both `app.mydomain.com` and `api.mydomain.com` can access the shared services.
149
150## Per-App vs Shared Attachments
151
152### Per-App Attachments
153
154Each app gets its own isolated service instances:
155
156```toml
157[attachments]
158"app.mydomain.com" = ["postgres:latest"]   # App's own postgres
159"api.mydomain.com" = ["postgres:latest"]   # API's own postgres
160```
161
162### Shared Attachments
163
164Multiple apps share the same service instances:
165
166```toml
167[network_groups]
168"backend" = ["app.mydomain.com", "api.mydomain.com"]
169
170[attachments]
171"backend" = ["postgres:latest", "redis:latest"]  # Shared by both
172```
173
174### Mixed Approach
175
176```toml
177[network_groups]
178"backend" = ["app.mydomain.com", "api.mydomain.com"]
179
180[attachments]
181"backend" = ["redis:latest"]                   # Shared cache
182"app.mydomain.com" = ["postgres:latest"]       # App's own database
183"api.mydomain.com" = ["postgres:latest"]       # API's own database
184```
185
186## Examples
187
188### Web App with Database
189
190```toml
191[routes]
192"app.mydomain.com" = "myapp:latest"
193
194[attachments]
195"app.mydomain.com" = ["postgres:18", "redis:7-alpine"]
196```
197
198### Microservices with Shared Queue
199
200```toml
201[routes]
202"orders.mydomain.com" = "orders-service:latest"
203"inventory.mydomain.com" = "inventory-service:latest"
204"notifications.mydomain.com" = "notifications-service:latest"
205
206[network_groups]
207"services" = ["orders.mydomain.com", "inventory.mydomain.com", "notifications.mydomain.com"]
208
209[attachments]
210"services" = ["rabbitmq:3-management"]
211"orders.mydomain.com" = ["orders-db:latest"]
212"inventory.mydomain.com" = ["inventory-db:latest"]
213```
214
215### Custom Service Images
216
217Build custom service images with your configuration:
218
219```dockerfile
220# my-postgres.Dockerfile
221FROM postgres:18
222VOLUME ["/var/lib/postgresql/data"]
223ENV POSTGRES_DB=production
224ENV POSTGRES_USER=appuser
225# Password should come from env file
226```
227
228```dockerfile
229# my-redis.Dockerfile
230FROM redis:7-alpine
231VOLUME ["/data"]
232CMD ["redis-server", "--appendonly", "yes"]
233```
234
235```toml
236[attachments]
237"app.mydomain.com" = ["my-postgres:latest", "my-redis:latest"]
238```
239
240## Container Naming
241
242Attachment containers are named:
243- `gordon-<domain>-<image-name>`
244- Example: `gordon-app-mydomain-com-postgres`
245
246## Labels
247
248Gordon adds labels to attachment containers:
249
250| Label | Value |
251|-------|-------|
252| `gordon.managed` | `true` |
253| `gordon.attachment` | `true` |
254| `gordon.attached-to` | Domain or group name |
255
256## CLI Management
257
258Manage attachments via the CLI without editing configuration files.
259
260### List Attachments
261
262```bash
263# List all attachments
264gordon attachments list
265
266# List attachments for a specific domain or network group
267gordon attachments list app.mydomain.com
268gordon attachments list backend
269
270# Remote mode
271gordon attachments list --remote https://gordon.mydomain.com --token $TOKEN
272```
273
274### Add Attachments
275
276```bash
277# Add attachment to a domain
278gordon attachments add app.mydomain.com postgres:18
279
280# Add attachment to a network group
281gordon attachments add backend redis:7-alpine
282
283# Remote mode
284gordon attachments add app.mydomain.com postgres:18 --remote https://gordon.mydomain.com --token $TOKEN
285```
286
287### Remove Attachments
288
289```bash
290# Remove attachment from a domain
291gordon attachments remove app.mydomain.com postgres:18
292
293# Remove from network group
294gordon attachments remove backend redis:7-alpine
295
296# Remote mode
297gordon attachments remove app.mydomain.com postgres:18 --remote https://gordon.mydomain.com --token $TOKEN
298```
299
300### Alias
301
302The `gordon attach` command is an alias for `gordon attachments`:
303
304```bash
305gordon attach list
306gordon attach add app.mydomain.com postgres:18
307gordon attach remove app.mydomain.com postgres:18
308```
309
310## Related
311
312- [Network Isolation](./network-isolation.md)
313- [Network Groups](./network-groups.md)
314- [Routes](./routes.md)
315- [CLI Commands](/docs/cli/index.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