Skip to content

Commit 4b0bb83

Browse files
committed
docs: update README for envsubst template system
1 parent a53ab54 commit 4b0bb83

1 file changed

Lines changed: 109 additions & 66 deletions

File tree

README.md

Lines changed: 109 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,27 @@ Your actual application code lives in separate repositories. The workflow checks
3535

3636
```
3737
This repo (deployment orchestrator)
38-
├── .github/workflows/deploy.yml ← the pipeline
38+
├── .github/workflows/deploy.yml ← the pipeline
3939
├── zerops/
4040
│ ├── recipes/
41-
│ │ ├── zerops-import-dev.yml ← lightweight config for dev environments
42-
│ │ ├── zerops-import-stage.yml ← mid-tier config for staging
43-
│ │ └── zerops-import-prod.yml ← full config for production (HA, dedicated CPU)
41+
│ │ ├── zerops-import.yml.tpl ← single template for all environments
42+
│ │ ├── env-dev.env ← variable values for dev (lightweight)
43+
│ │ ├── env-stage.env ← variable values for staging (mid-tier)
44+
│ │ └── env-prod.env ← variable values for production (HA, dedicated CPU)
4445
│ └── scripts/
45-
│ └── zerops-api.sh ← API helper functions (curl + jq, no CLI needed)
46+
│ └── zerops-api.sh ← API helper functions (curl + jq, no CLI needed)
4647
4748
Your app repos (separate repositories)
48-
├── your-org/your-app ← e.g. Bun/Node web app, has zerops.yml
49-
└── your-org/your-worker ← e.g. Python worker, has zerops.yml
49+
├── your-org/your-app ← e.g. Bun/Node web app, has zerops.yml
50+
└── your-org/your-worker ← e.g. Python worker, has zerops.yml
5051
```
5152

5253
### What Happens When You Push a Feature Branch
5354

5455
1. GitHub Actions detects the push
5556
2. The workflow checks out this repo + your app repos
5657
3. It calls the Zerops API to check if a project named `showcase-dev-{branch}` exists
57-
4. If not, it creates one using the dev recipe (PostgreSQL, Valkey/Redis, NATS, object storage, plus your app and worker services)
58+
4. If not, it renders the import template (`zerops-import.yml.tpl`) with dev values (`env-dev.env`) and creates the project (PostgreSQL, Valkey/Redis, NATS, object storage, plus your app and worker services)
5859
5. It waits for all infrastructure services to be ready
5960
6. It packages your app code into a tarball, uploads it to Zerops, and triggers a build + deploy
6061
7. It does the same for your worker service
@@ -90,7 +91,7 @@ If you're new to Zerops, here are the key concepts this pipeline uses:
9091

9192
**Service (Service Stack)** — A single component within a project: a web app, a database, a cache, etc. Each service has a type (like `postgresql@17` or `bun@1.2`) and configuration for scaling, memory, and storage.
9293

93-
**Import YAML** A file that describes a complete project: its name and all its services. Zerops can create an entire project from this file in one API call. The recipes in `zerops/recipes/` are import YAML files.
94+
**Import YAML** -- A file that describes a complete project: its name and all its services. Zerops can create an entire project from this file in one API call. In this pipeline, the import YAML is generated from a template (`zerops-import.yml.tpl`) combined with per-environment variables (`env-dev.env`, `env-stage.env`, `env-prod.env`).
9495

9596
**zerops.yml** — A file in your app's repository that tells Zerops how to build and run your application. It defines build commands, runtime commands, and which files to deploy. This is analogous to a Dockerfile, but simpler.
9697

@@ -150,90 +151,131 @@ env:
150151
151152
`ZEROPS_YAML_SETUP` must match the `setup` field in your app's `zerops.yml`. For example, if your `zerops.yml` starts with `zerops: - setup: prod`, set this to `prod`.
152153

153-
#### 2. Customize the import recipes
154+
#### 2. Understand the template system
154155

155-
The three recipe files define what services each environment gets. Edit them to match your application's needs.
156+
Instead of maintaining three separate recipe files (one per environment), this pipeline uses a single template with per-environment variable files. You edit services in one place and only vary the values that differ.
156157

157-
**`zerops/recipes/zerops-import-dev.yml`** — Lightweight, for development:
158+
```
159+
zerops/recipes/
160+
zerops-import.yml.tpl <- the template (shared structure)
161+
env-dev.env <- values for dev environments
162+
env-stage.env <- values for staging
163+
env-prod.env <- values for production
164+
```
165+
166+
The template uses `${VARIABLE}` placeholders that get replaced by [`envsubst`](https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html) at deploy time. Lines where the value is empty are removed automatically -- this is how optional fields like `corePackage`, `cpuMode`, or `minContainers` only appear in environments that set them.
167+
168+
#### 3. Customize the template
169+
170+
Open `zerops/recipes/zerops-import.yml.tpl`. This defines the structure of every environment:
158171
159172
```yaml
160173
project:
161-
name: <FILLED_BY_PIPELINE> # ← don't change this, the pipeline fills it in
174+
name: ${PROJECT_NAME}
175+
corePackage: ${CORE_PACKAGE}
162176
163177
services:
164178
- hostname: app
165-
type: bun@1.2 # ← change to your app's runtime
166-
enableSubdomainAccess: true # ← gives it a public URL
167-
168-
- hostname: worker
169-
type: python@3.12 # ← change to your worker's runtime
179+
type: ${APP_TYPE}
180+
enableSubdomainAccess: true
181+
envSecrets:
182+
CORE_MODE: ${APP_CORE_MODE}
183+
minContainers: ${APP_MIN_CONTAINERS}
184+
maxContainers: ${APP_MAX_CONTAINERS}
185+
verticalAutoscaling:
186+
cpuMode: ${APP_CPU_MODE}
187+
minRam: ${APP_MIN_RAM}
188+
minFreeRamGB: ${APP_MIN_FREE_RAM}
170189
171190
- hostname: db
172-
type: postgresql@17
173-
mode: NON_HA # ← single instance (cheaper for dev)
174-
priority: 10 # ← infrastructure: created first, never redeployed
175-
176-
- hostname: redis
177-
type: valkey@7.2
178-
mode: NON_HA
191+
type: ${DB_TYPE}
192+
mode: ${DB_MODE}
179193
priority: 10
194+
# ... more services
195+
```
180196

181-
- hostname: queue
182-
type: nats@2.12
183-
mode: NON_HA
184-
priority: 10
197+
Change the `hostname`, `type`, and structure to match your application. Add or remove services as needed. Use `${VARIABLE_NAME}` for any value that should differ between environments.
185198

186-
- hostname: storage
187-
type: object-storage
188-
objectStorageSize: 2 # ← 2 GB
189-
objectStoragePolicy: public-read
190-
priority: 10
199+
**Important:** `${PROJECT_NAME}` is set automatically by the pipeline. Do not hardcode it.
200+
201+
**Important:** Services with `priority: 10` are infrastructure. They are created on first deploy and never touched again. Only services without a `priority` (app, worker) receive code deployments.
202+
203+
#### 4. Customize the env files
204+
205+
Each env file is a simple `KEY=VALUE` list. Here's what the dev file looks like:
206+
207+
```bash
208+
# env-dev.env -- lightweight, single-instance, shared CPU
209+
210+
CORE_PACKAGE= # empty = field is omitted from the YAML
211+
APP_TYPE=bun@1.2
212+
APP_CORE_MODE= # empty = envSecrets block omitted
213+
APP_MIN_CONTAINERS= # empty = no auto-scaling
214+
APP_MAX_CONTAINERS=
215+
APP_CPU_MODE= # empty = shared CPU (default)
216+
APP_MIN_RAM=0.5
217+
APP_MIN_FREE_RAM=0.25
218+
DB_TYPE=postgresql@17
219+
DB_MODE=NON_HA # single instance
220+
# ... etc
191221
```
192222

193-
**`zerops/recipes/zerops-import-prod.yml`** — Full production config:
223+
And the prod file sets everything:
224+
225+
```bash
226+
# env-prod.env -- full HA, dedicated CPU, auto-scaling
227+
228+
CORE_PACKAGE=SERIOUS # higher resource tier
229+
APP_TYPE=bun@1.2
230+
APP_CORE_MODE=serious
231+
APP_MIN_CONTAINERS=2 # auto-scaling: 2-6 containers
232+
APP_MAX_CONTAINERS=6
233+
APP_CPU_MODE=DEDICATED # dedicated CPU
234+
APP_MIN_RAM=1
235+
APP_MIN_FREE_RAM=0.5
236+
DB_TYPE=postgresql@17
237+
DB_MODE=HA # high availability (3 replicas)
238+
DB_CPU_MODE=DEDICATED
239+
DB_MIN_RAM=2
240+
# ... etc
241+
```
242+
243+
The rule is simple: **set a value and it appears in the YAML; leave it empty and the entire line is removed.** Section headers like `verticalAutoscaling:` or `envSecrets:` are also removed automatically if all their children are empty.
244+
245+
This means the generated YAML for dev is clean and minimal:
194246

195247
```yaml
248+
# What the pipeline actually sends to Zerops for a dev environment:
196249
project:
197-
name: <FILLED_BY_PIPELINE>
198-
corePackage: SERIOUS # ← higher resource tier
250+
name: showcase-dev-feat-login
199251

200252
services:
201253
- hostname: app
202254
type: bun@1.2
203255
enableSubdomainAccess: true
204-
minContainers: 2 # ← auto-scaling: 2-6 containers
205-
maxContainers: 6
206256
verticalAutoscaling:
207-
cpuMode: DEDICATED # ← dedicated CPU for production
208-
minRam: 1
209-
minFreeRamGB: 0.5
257+
minRam: 0.5
258+
minFreeRamGB: 0.25
259+
260+
- hostname: worker
261+
type: python@3.12
262+
verticalAutoscaling:
263+
minRam: 0.5
210264

211265
- hostname: db
212266
type: postgresql@17
213-
mode: HA # ← high availability (3 replicas)
267+
mode: NON_HA
214268
priority: 10
215-
verticalAutoscaling:
216-
cpuMode: DEDICATED
217-
minRam: 2
218-
minFreeRamGB: 1
219-
220-
# ... similar patterns for other services
269+
# ... etc
221270
```
222271

223-
The key differences between environments:
224-
- **Dev**: `NON_HA` (single instance), shared CPU, minimal RAM
225-
- **Stage**: `NON_HA`, shared CPU, moderate RAM
226-
- **Prod**: `HA` (high availability), `DEDICATED` CPU, more RAM, auto-scaling
227-
228-
**Important:** The `<FILLED_BY_PIPELINE>` placeholder in the project name is replaced automatically. Do not change it.
229-
230-
**Important:** Services with `priority: 10` are infrastructure services. They are created when the project is first set up and are never touched by subsequent deploys. Only services without a `priority` (app, worker) receive code deployments.
272+
While prod gets the full config with HA, dedicated CPU, auto-scaling, and envSecrets -- all from the same template.
231273

232-
#### 3. Add or remove services
274+
#### 5. Add or remove services
233275

234-
If your app doesn't use a message queue, remove the `queue` service from all three recipe files. If you need MySQL instead of PostgreSQL, change the `type` field. The available service types are listed in the [Zerops documentation](https://docs.zerops.io).
276+
To add a service, add it to the template and define its variables in all three env files. To remove one (e.g., no message queue), delete it from the template.
235277

236-
If you have more than two app services (e.g., an API, a frontend, and a worker), add them to the recipes and duplicate the deploy steps in the workflow:
278+
If you have more than two app services (e.g., an API, a frontend, and a worker), add them to the template and duplicate the deploy steps in the workflow:
237279

238280
```yaml
239281
# In the workflow, after deploying app and worker:
@@ -243,7 +285,7 @@ zerops_deploy_service "$SERVICE_FOUND_ID" ./frontend ./frontend/zerops.yml "$ZER
243285

244286
And add a checkout step for the additional repo.
245287

246-
#### 4. Change the project naming prefix
288+
#### 6. Change the project naming prefix
247289

248290
The project names (`showcase-dev-*`, `showcase-stage`, `showcase-prod`, `showcase-mr-*`) are set in the workflow's deploy steps. Search for `showcase` and replace with your project's name, e.g., `myapp-dev-*`, `myapp-stage`, etc.
249291

@@ -335,9 +377,10 @@ The file `zerops/scripts/zerops-api.sh` contains all the functions that talk to
335377
336378
| Function | What it does |
337379
|---|---|
338-
| `zerops_ensure_project` | Find a project by name; create it if it doesn't exist. This is the core function that keeps permanent environments safe. |
380+
| `zerops_render_template` | Render the import template with an env file using `envsubst`. Strips empty values and orphaned section headers. Sets `RENDERED_YAML`. |
381+
| `zerops_ensure_project` | Find a project by name; create it if it doesn't exist. Uses `RENDERED_YAML` if set, otherwise falls back to a static import file. This is the core function that keeps permanent environments safe. |
339382
| `zerops_find_project` | Look up a project by name. Sets `PROJECT_FOUND_ID` if found. |
340-
| `zerops_import_project` | Create a new project from an import YAML file. |
383+
| `zerops_import_project` | Create a new project from rendered or file-based YAML. |
341384
| `zerops_delete_project` | Delete a project by ID. |
342385
| `zerops_find_service` | Look up a service within a project by hostname. |
343386
| `zerops_deploy_service` | Full deploy cycle: create app version, package code, upload, build, deploy, poll until complete. |
@@ -355,9 +398,9 @@ The file `zerops/scripts/zerops-api.sh` contains all the functions that talk to
355398
356399
If your project has only one deployable service (no separate worker), remove the worker-related lines from:
357400
358-
1. The workflow remove the worker checkout step and the `zerops_find_service`/`zerops_deploy_service` lines for the worker in each job
359-
2. The recipes — remove the `worker` service definition
360-
3. The workflow env remove the `WORKER_REPO` and `WORKER_SERVICE_NAME` variables
401+
1. The workflow -- remove the worker checkout step and the `zerops_find_service`/`zerops_deploy_service` lines for the worker in each job
402+
2. The template -- remove the `worker` service block from `zerops-import.yml.tpl` and worker variables from the env files
403+
3. The workflow env -- remove the `WORKER_REPO` and `WORKER_SERVICE_NAME` variables
361404
362405
## FAQ
363406

0 commit comments

Comments
 (0)