v2.2.4 af396ea

postgres-service.md RawHistoryFolder

Add PostgreSQL to Your App

Add a PostgreSQL database as an attachment to your application.

What You'll Learn

  • Creating a custom PostgreSQL image with persistence
  • Configuring attachments
  • Connecting your app to the database

Prerequisites

Steps

1. Create a Custom PostgreSQL Image

Create postgres.Dockerfile:

FROM postgres:15-alpine

# Persistent data directory
VOLUME ["/var/lib/postgresql/data"]

# Default configuration (override via env file)
ENV POSTGRES_DB=myapp
ENV POSTGRES_USER=myapp

2. Build and Push PostgreSQL Image

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

3. Configure the Attachment

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

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

[network_isolation]
enabled = true

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

Reload Gordon:

gordon reload

4. Configure Database Password

Create an env file for your app at ~/.gordon/env/app_mydomain_com.env:

# Application settings
NODE_ENV=production
PORT=3000

# Database connection
DATABASE_HOST=my-postgres
DATABASE_PORT=5432
DATABASE_NAME=myapp
DATABASE_USER=myapp
DATABASE_PASSWORD=your-secure-password

# Full connection URL
DATABASE_URL=postgresql://myapp:your-secure-password@my-postgres:5432/myapp

Create an env file for PostgreSQL at ~/.gordon/env/my-postgres.env:

POSTGRES_PASSWORD=your-secure-password

5. Update Your Application

Connect to the database using the service name:

// Node.js with pg
const { Pool } = require('pg');

const pool = new Pool({
  host: process.env.DATABASE_HOST || 'my-postgres',
  port: process.env.DATABASE_PORT || 5432,
  database: process.env.DATABASE_NAME || 'myapp',
  user: process.env.DATABASE_USER || 'myapp',
  password: process.env.DATABASE_PASSWORD,
});
# Python with psycopg2
import os
import psycopg2

conn = psycopg2.connect(
    host=os.environ.get('DATABASE_HOST', 'my-postgres'),
    port=os.environ.get('DATABASE_PORT', 5432),
    database=os.environ.get('DATABASE_NAME', 'myapp'),
    user=os.environ.get('DATABASE_USER', 'myapp'),
    password=os.environ['DATABASE_PASSWORD'],
)

6. Rebuild and Deploy

docker build -t my-app .
docker tag my-app registry.mydomain.com/my-app:latest
docker push registry.mydomain.com/my-app:latest

Verify Connection

Check Containers

docker ps | grep gordon-app-mydomain

You should see both your app and postgres containers.

Check Network

docker network inspect gordon-app-mydomain-com

Both containers should be in the same network.

Test Connection

SSH to your server and test:

docker exec -it gordon-app-mydomain-com-my-postgres psql -U myapp -d myapp

Using Secrets

For production, use a secrets backend instead of plain text:

# Store password in pass
pass insert myapp/db-password

Update env file:

DATABASE_PASSWORD=${pass:myapp/db-password}
POSTGRES_PASSWORD=${pass:myapp/db-password}

Data Persistence

PostgreSQL data persists in a Docker volume:

# List volumes
docker volume ls | grep postgres

# Volume name: gordon-app-mydomain-com-my-postgres-var-lib-postgresql-data

Data survives:

  • Container restarts
  • App updates
  • Gordon reloads

Backup and Restore

Backup

docker exec gordon-app-mydomain-com-my-postgres \
  pg_dump -U myapp myapp > backup.sql

Restore

cat backup.sql | docker exec -i gordon-app-mydomain-com-my-postgres \
  psql -U myapp myapp

Next Steps

source
1# Add PostgreSQL to Your App
2
3Add 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
20Create `postgres.Dockerfile`:
21
22```dockerfile
23FROM postgres:15-alpine
24
25# Persistent data directory
26VOLUME ["/var/lib/postgresql/data"]
27
28# Default configuration (override via env file)
29ENV POSTGRES_DB=myapp
30ENV POSTGRES_USER=myapp
31```
32
33### 2. Build and Push PostgreSQL Image
34
35```bash
36docker build -f postgres.Dockerfile -t my-postgres .
37docker tag my-postgres registry.mydomain.com/my-postgres:latest
38docker push registry.mydomain.com/my-postgres:latest
39```
40
41### 3. Configure the Attachment
42
43Edit `~/.config/gordon/gordon.toml`:
44
45```toml
46[routes]
47"app.mydomain.com" = "my-app:latest"
48
49[network_isolation]
50enabled = true
51
52[attachments]
53"app.mydomain.com" = ["my-postgres:latest"]
54```
55
56Reload Gordon:
57
58```bash
59gordon reload
60```
61
62### 4. Configure Database Password
63
64Create an env file for your app at `~/.gordon/env/app_mydomain_com.env`:
65
66```bash
67# Application settings
68NODE_ENV=production
69PORT=3000
70
71# Database connection
72DATABASE_HOST=my-postgres
73DATABASE_PORT=5432
74DATABASE_NAME=myapp
75DATABASE_USER=myapp
76DATABASE_PASSWORD=your-secure-password
77
78# Full connection URL
79DATABASE_URL=postgresql://myapp:your-secure-password@my-postgres:5432/myapp
80```
81
82Create an env file for PostgreSQL at `~/.gordon/env/my-postgres.env`:
83
84```bash
85POSTGRES_PASSWORD=your-secure-password
86```
87
88### 5. Update Your Application
89
90Connect to the database using the service name:
91
92```javascript
93// Node.js with pg
94const { Pool } = require('pg');
95
96const 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
107import os
108import psycopg2
109
110conn = 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
122docker build -t my-app .
123docker tag my-app registry.mydomain.com/my-app:latest
124docker push registry.mydomain.com/my-app:latest
125```
126
127## Verify Connection
128
129### Check Containers
130
131```bash
132docker ps | grep gordon-app-mydomain
133```
134
135You should see both your app and postgres containers.
136
137### Check Network
138
139```bash
140docker network inspect gordon-app-mydomain-com
141```
142
143Both containers should be in the same network.
144
145### Test Connection
146
147SSH to your server and test:
148
149```bash
150docker exec -it gordon-app-mydomain-com-my-postgres psql -U myapp -d myapp
151```
152
153## Using Secrets
154
155For production, use a secrets backend instead of plain text:
156
157```bash
158# Store password in pass
159pass insert myapp/db-password
160```
161
162Update env file:
163
164```bash
165DATABASE_PASSWORD=${pass:myapp/db-password}
166POSTGRES_PASSWORD=${pass:myapp/db-password}
167```
168
169## Data Persistence
170
171PostgreSQL data persists in a Docker volume:
172
173```bash
174# List volumes
175docker volume ls | grep postgres
176
177# Volume name: gordon-app-mydomain-com-my-postgres-var-lib-postgresql-data
178```
179
180Data survives:
181- Container restarts
182- App updates
183- Gordon reloads
184
185## Backup and Restore
186
187### Backup
188
189```bash
190docker exec gordon-app-mydomain-com-my-postgres \
191  pg_dump -U myapp myapp > backup.sql
192```
193
194### Restore
195
196```bash
197cat 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)

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