You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
recipe(cloud-connect-dev): demonstrate secrets sync against a local Postgres
The recipe gains a step where the portal delivers the password for a local
Postgres dataset: the Spicepod names `${secrets:pg_password}`, the value is set
once in the project's secrets and arrives with the deployment, and nothing about
the database password is written to the repository or to the instance's config
in plaintext.
It teaches the whole model rather than the happy path — that no `secrets:`
section is needed because the delivered store is built in at the lowest
precedence, that a local env override still wins, that a first delivery resolves
live while a rotation of an already-resolved value waits for a restart, and that
the encrypted local cache is what lets that restart succeed with the gateway
unreachable.
Connect this directory to Spice Cloud with one command, watch a deployment apply to the running instance without a restart, stop it, and reconnect.
6
6
7
-
The recipe is deliberately minimal: one public dataset, no infrastructure, no credentials on disk. What it demonstrates is the [Cloud Connect](https://spiceai.org/docs/deployment/cloud-connect) lifecycle on the machine you develop on.
7
+
The recipe stays small: one public dataset, one local PostgreSQL in Docker, and no credential written to any file it tracks. What it demonstrates is the [Cloud Connect](https://spiceai.org/docs/deployment/cloud-connect) lifecycle on the machine you develop on, including how a deployment delivers the secrets its configuration needs.
8
8
9
9
You will:
10
10
11
11
1. Connect this directory and get a running instance attached to a new Spice Cloud project.
12
12
2. Deploy a change that applies live, with no restart and no serving gap.
13
-
3. Deploy a change that needs a restart, and see exactly which settings are pending.
14
-
4. Stop with `Ctrl-C` and reconnect with `spice run` — the identity survives.
15
-
5. Release the instance and delete its project.
13
+
3. Deliver a secret with a deployment, and query a local PostgreSQL whose password only ever existed in your shell and in the portal.
14
+
4. Deploy a change that needs a restart, and see exactly which settings are pending.
15
+
5. Stop with `Ctrl-C` and reconnect with `spice run` — the identity survives.
16
+
6. Release the instance and delete its project.
16
17
17
18
## Prerequisites
18
19
@@ -24,10 +25,11 @@ You will:
24
25
25
26
- A Spice Cloud account with the **owner** or **admin** role in at least one organization. Those roles may enroll an instance and create a project; `member` may not.
26
27
- Outbound HTTPS to `api.spice.ai` and to the Cloud Connect gateway.
28
+
- Docker, for the local PostgreSQL in step 3. Steps 1, 2 and 4 onwards do not need it.
27
29
28
30
No `spice login` beforehand — `spice connect` runs login inline if needed.
29
31
30
-
**Cost:** a Spice Cloud project on the free tier. Step 6 deletes it.
32
+
**Cost:** a Spice Cloud project on the free tier. Step 7 deletes it, and the database is a local container.
31
33
32
34
## 1. Connect
33
35
@@ -135,7 +137,125 @@ SELECT count(*) FROM recent;
135
137
secrets: none (the last deployment delivered no secrets)
136
138
```
137
139
138
-
## 3. Deploy a change that needs a restart
140
+
## 3. Deliver a secret with a deployment
141
+
142
+
A deployment carries configuration **and** the secrets that configuration needs. This step connects a local PostgreSQL as a dataset whose password is never written in the Spicepod, never committed, and never typed on this machine after the first line below.
143
+
144
+
Start the database. The password lives in your shell, and the container is the only thing on this machine that receives it:
|`pg_password`| the value of `$SPICE_DEMO_PG_PASSWORD`|
169
+
170
+
Then add the dataset to the project's Spicepod and deploy:
171
+
172
+
```yaml
173
+
datasets:
174
+
- from: postgres:public.orders
175
+
name: orders
176
+
params:
177
+
pg_host: localhost
178
+
pg_port: "55432"
179
+
pg_db: spice_demo
180
+
pg_user: spice_reader
181
+
pg_pass: ${secrets:pg_password}
182
+
pg_sslmode: disable # a local container, so there is no TLS to verify
183
+
```
184
+
185
+
The deployment applies live — a dataset is a live section, and a secret this instance did not hold yet is installed the moment it arrives, so the dataset resolves its password as it loads:
186
+
187
+
```console
188
+
INFO Spice Cloud Connect: the deployment delivered 1 secret(s) this instance did not hold, now resolvable: pg_password
189
+
INFO runtime::secrets_preflight: Secrets: all 1 secret reference(s) resolved.
190
+
INFO runtime::init::dataset: Dataset orders registered (postgres:public.orders), results cache enabled. duration_ms=0
191
+
```
192
+
193
+
The PID is still the one from step 1. Query the table the portal just wired up:
194
+
195
+
```bash
196
+
spice sql
197
+
```
198
+
199
+
```sql
200
+
SELECT customer, total_cents FROM orders ORDER BY id LIMIT3;
`${secrets:pg_password}` resolves without declaring anything, because delivered secrets are a **built-in** store rather than one a Spicepod configures. That is what keeps the same Spicepod portable: it runs unchanged as a managed app and on this self-hosted instance, with no `secrets:` block that would be meaningless on the other.
230
+
231
+
### A local value still wins
232
+
233
+
The delivered store is registered at the **lowest** precedence, so any store you configure yourself — `env`, a `.env.local` file, a keyring — keeps its value for the same key. Delivery adds a source; it does not take your override away:
234
+
235
+
```bash
236
+
# The env store maps ${secrets:pg_password} to PG_PASSWORD, uppercased.
237
+
export PG_PASSWORD="$SPICE_DEMO_PG_PASSWORD"
238
+
```
239
+
240
+
That is how you keep working against the same Spicepod with no portal round trip. Unset it before continuing, or the delivered value is never the one in use:
241
+
242
+
```bash
243
+
unset PG_PASSWORD
244
+
```
245
+
246
+
### If you deploy the dataset before setting the secret
247
+
248
+
The runtime says exactly what is missing and which stores it searched, and the dataset is the only thing that fails:
249
+
250
+
```console
251
+
WARN runtime::secrets_preflight: Secrets: 1 of 1 secret reference(s) could not be resolved:
252
+
✗ ${ secrets:pg_password } dataset 'orders' (pg_pass) — not found in [env, cloud]
253
+
ERROR runtime::init::dataset: Error initializing dataset orders. Failed to initialize data connector: Cannot connect to the dataset orders (postgres). Authentication failed.
254
+
```
255
+
256
+
Set the secret in the portal and deploy again — the instance keeps serving everything else throughout.
257
+
258
+
## 4. Deploy a change that needs a restart
139
259
140
260
Some sections are read only when the runtime starts. Add one in the portal — for example a `runtime` setting — and deploy again:
The PID is still unchanged, and queries still work. In the foreground there is no supervisor to restart the instance — that is step 4.
307
+
The PID is still unchanged, and queries still work. In the foreground there is no supervisor to restart the instance — that is step 5.
308
+
309
+
### A rotated secret is the same shape
310
+
311
+
The first delivery of `pg_password` in step 3 applied live because nothing had resolved it yet. Rotating it is different: the components holding the old value keep it until they are built again, so a rotation is pending in exactly the way a `runtime` setting is. Change the password in the database, in the portal secret, and deploy:
-c "ALTER ROLE spice_reader WITH PASSWORD 'the-new-value';"
316
+
```
317
+
318
+
```console
319
+
restart: required for secrets
320
+
```
321
+
322
+
The instance goes on serving with the connection it already has. The restart in step 5 is what picks the new value up — and it reads it from the encrypted cache in `.spice/`, so it succeeds even with no route to Spice Cloud.
188
323
189
-
## 4. Stop and reconnect
324
+
## 5. Stop and reconnect
190
325
191
326
In the foreground terminal, press `Ctrl-C`. The runtime stops. The Cloud identity is **not** released: the instance shows as offline in the portal and `.spice/identity.json` is still on disk.
192
327
@@ -219,7 +354,7 @@ The `restart:` line is gone.
219
354
220
355
`spice connect` from this directory does the same thing. It never enrolls a second instance — an existing identity always wins.
221
356
222
-
## 5. Validate locally
357
+
## 6. Validate locally
223
358
224
359
`validate.sh` checks the parts of this recipe that need no Spice Cloud account and no credentials. Run it any time:
225
360
@@ -229,7 +364,7 @@ The `restart:` line is gone.
229
364
230
365
It asserts that the CLI is new enough, that the project-name default derives from this directory with no random fallback, that `spice connect status` reports a coherent snapshot in both output formats, that a non-interactive `spice connect` refuses instead of hanging, and that no credential or generated identity is staged for commit.
231
366
232
-
## 6. Clean up
367
+
## 7. Clean up
233
368
234
369
Stop the runtime first — removal refuses while `spiced` is running — then release the instance:
235
370
@@ -256,9 +391,17 @@ spice connect status
256
391
Spice Cloud Connect: not connected (<path>/cloud-connect-dev)
257
392
```
258
393
394
+
Then stop the database and drop its volume:
395
+
396
+
```bash
397
+
docker compose down -v
398
+
unset SPICE_DEMO_PG_PASSWORD
399
+
```
400
+
259
401
## Notes
260
402
261
403
-**Nothing is committed.**`.spice/` is gitignored: the issued identity, the cloud-managed Spicepod, and the delivered-secrets cache all live there. `validate.sh` checks it.
404
+
-**A delivered value is never in plaintext on disk.** The cache under `.spice/` is sealed with a key derived from this instance's identity, and it is what makes a restart work without asking the control plane for the secret again. `spice connect status` reports delivered secret **names**; the values stay inside the runtime process.
262
405
-**`spice connect` needs a terminal.** It is interactive; on a non-interactive stdin it exits and points at `spiced --token <enrollment-key>` for [headless enrollment](https://spiceai.org/docs/deployment/cloud-connect/headless).
263
406
-**Cancelling is safe.**`Esc` or `Ctrl-C` at any prompt is a clean exit. An interrupted enrollment resumes on the next run rather than creating a duplicate instance or project.
264
407
-**This recipe is foreground only.** To keep the instance running across reboots, install the managed service — systemd on Linux, launchd on macOS. See [Cloud Connect as a persistent service](https://spiceai.org/docs/deployment/cloud-connect/service). Windows has no managed service; there, the foreground flow in this recipe is the development story.
0 commit comments