initium 1.0.4

Swiss-army toolbox for Kubernetes initContainers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# Frequently Asked Questions

## Functionality

### What is Initium and when should I use it?

Initium is a single binary that replaces ad-hoc bash scripts in Kubernetes `initContainers`. Use it when your pod needs to do any of these before the main container starts:

- Wait for a database or API to become reachable
- Run database migrations or seed data
- Render config files from templates
- Fetch secrets or config from an HTTP endpoint
- Run a setup script with structured logging

### How do I wait for Postgres to be ready before my app starts?

Add an Initium initContainer that targets the Postgres TCP port:

```yaml
initContainers:
  - name: wait-for-postgres
    image: ghcr.io/kitstream/initium:latest
    args: ["wait-for", "--target", "tcp://postgres:5432", "--timeout", "120s"]
    securityContext:
      runAsNonRoot: true
      runAsUser: 65534
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: [ALL]
```

Initium retries with exponential backoff (default: up to 60 attempts, 1s initial delay, 30s max delay) until the connection succeeds or the timeout expires.

### How do I wait for an HTTP health endpoint instead of a TCP port?

Use an `http://` or `https://` target instead of `tcp://`:

```yaml
args: ["wait-for", "--target", "http://config-service:8080/healthz"]
```

By default Initium expects HTTP 200. To accept a different status code:

```yaml
args: ["wait-for", "--target", "http://api:8080/ready", "--http-status", "204"]
```

### Can I wait for multiple services at once?

Yes. Pass multiple `--target` flags. They are checked sequentially — all must succeed:

```yaml
args:
  - wait-for
  - --target
  - tcp://postgres:5432
  - --target
  - tcp://redis:6379
  - --target
  - http://config-service:8080/healthz
```

If any target fails to become reachable within the timeout, the initContainer exits with a non-zero code and the pod will not start.

### How do I seed data into my database?

Use the `seed` subcommand. It wraps your existing seed tool and forwards its exit code:

```yaml
initContainers:
  - name: seed-data
    image: ghcr.io/kitstream/initium:latest
    args: ["seed", "--", "/app/seed", "--file", "/seeds/initial.sql"]
    env:
      - name: DATABASE_URL
        valueFrom:
          secretKeyRef:
            name: db-credentials
            key: url
```

Everything after `--` is the command Initium will execute. Initium does not interpret those arguments — it passes them directly via `execve`.

### How do I run database migrations?

Use the `migrate` subcommand. It works the same way as `seed` but is a separate subcommand so you can distinguish migration steps from seed steps in logs:

```yaml
initContainers:
  - name: migrate
    image: ghcr.io/kitstream/initium:latest
    args: ["migrate", "--", "flyway", "migrate"]
    env:
      - name: FLYWAY_URL
        value: "jdbc:postgresql://postgres:5432/mydb"
```

### How do I render a config file from a template before my app starts?

Use the `render` subcommand. Mount a ConfigMap containing your template and let Initium expand environment variables into the output:

```yaml
initContainers:
  - name: render-config
    image: ghcr.io/kitstream/initium:latest
    args:
      - render
      - --template
      - /templates/app.conf.tmpl
      - --output
      - app.conf
      - --workdir
      - /work
    env:
      - name: DB_HOST
        value: postgres
      - name: DB_PORT
        value: "5432"
    volumeMounts:
      - name: workdir
        mountPath: /work
      - name: templates
        mountPath: /templates
        readOnly: true
```

The rendered file lands in `/work/app.conf`, which your main container can read from the shared `workdir` volume.

### How do I get JSON-formatted logs?

Add `--json` before the subcommand:

```yaml
args: ["--json", "wait-for", "--target", "tcp://postgres:5432"]
```

Output looks like:

```json
{"time":"2026-01-15T10:30:00Z","level":"INFO","msg":"target is reachable","target":"tcp://postgres:5432","attempts":"1"}
```

This is useful when you're shipping logs to a centralized system like Loki, Datadog, or Elasticsearch.

### How do I tune retry behavior?

All retry parameters are flags on the `wait-for` subcommand:

| Flag               | Default   | What it does                                                             |
| ------------------ | --------- | ------------------------------------------------------------------------ |
| `--max-attempts`   | `60`      | Total number of attempts before giving up                                |
| `--initial-delay`  | `1s`      | Delay after the first failure                                            |
| `--max-delay`      | `30s`     | Upper bound on delay between retries                                     |
| `--backoff-factor` | `2.0`     | Multiplier applied to the delay after each attempt                       |
| `--jitter`         | `0.1`     | Random fraction (0.0–1.0) added to each delay to prevent thundering herd |
| `--timeout`        | `5m`      | Hard deadline across all targets                                         |

Example — fast retries with low jitter:

```yaml
args:
  - wait-for
  - --target
  - tcp://postgres:5432
  - --max-attempts
  - "10"
  - --initial-delay
  - "200ms"
  - --max-delay
  - "2s"
  - --backoff-factor
  - "1.5"
  - --jitter
  - "0.05"
```

### What happens when a target never becomes reachable?

Initium exits with code `1` after exhausting all retry attempts or hitting the `--timeout` deadline. The last error is printed to stderr:

```
2026-01-15T10:32:00Z [ERROR] target not reachable target=tcp://postgres:5432 error=all 60 attempts failed, last error: tcp dial postgres:5432: dial tcp: connect: connection refused
```

Because the initContainer exits non-zero, Kubernetes will restart it according to the pod's `restartPolicy` (default: `Always` for Deployments).

### Can I use Initium outside of Kubernetes?

Yes. It is a standalone static binary. Common non-Kubernetes uses:

```bash
# Docker Compose — wait for a dependency before starting
docker run --rm --network mynet ghcr.io/kitstream/initium:latest \
  wait-for --target tcp://db:5432

# CI pipeline — gate a step on service readiness
./bin/initium wait-for --target http://localhost:8080/healthz --timeout 30s
```

### What is the `--` separator and why is it needed?

The `--` tells Initium where its own flags end and the wrapped command begins. Without it, Initium might try to interpret your tool's flags as its own:

```yaml
# Correct — Initium sees "migrate" subcommand, then passes "flyway migrate" to execve
args: ["migrate", "--", "flyway", "migrate"]

# Wrong — Initium tries to parse "flyway" as a flag to the migrate subcommand
args: ["migrate", "flyway", "migrate"]
```

This is the same convention used by `kubectl`, `docker`, and many other CLI tools.

### Does Initium run commands through a shell?

No. Commands passed after `--` are executed directly via the operating system's `execve` syscall. There is no `/bin/sh -c` wrapper. This means:

- Shell features like pipes (`|`), redirects (`>`), globbing (`*`), and variable expansion (`$VAR`) will **not** work
- This is intentional — it prevents shell injection attacks
- If you genuinely need shell features, wrap your script in a file and execute it: `args: ["exec", "--", "/bin/sh", "/scripts/setup.sh"]`

---

## Security

### Does Initium need root or any special privileges?

No. Initium is designed for the most restrictive Kubernetes security posture:

```yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 65534        # nobody
  runAsGroup: 65534
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  seccompProfile:
    type: RuntimeDefault
  capabilities:
    drop: [ALL]
```

This satisfies the Kubernetes `restricted` Pod Security Standard. No special PSPs, ClusterRoles, or RBAC bindings are required.

### Will my secrets show up in Initium's logs?

No. Initium automatically redacts values for log keys matching common secret patterns: `token`, `password`, `secret`, `auth`, `authorization`, `api_key`, `apikey`. These appear as `REDACTED` in both text and JSON log output.

However, Initium cannot redact secrets that appear as part of a URL or arbitrary string. Avoid embedding credentials directly in target URLs — use environment variables and your application's own config parsing instead.

### Can Initium write files outside the working directory?

No. All file-writing operations (`render`, `fetch`) are constrained to the path specified by `--workdir` (default: `/work`). Initium rejects:

- **Absolute target paths** like `/etc/passwd`
- **Path traversal** like `../../etc/passwd` or `sub/../../etc/shadow`

If a path escapes the workdir, Initium exits with an error and writes nothing.

### Can Initium make network requests I didn't ask for?

No. Initium has no default outbound connections. Every network target must be explicitly provided via `--target` or `--url` flags. There is no telemetry, no update checker, no default phone-home behavior.

### Why is TLS verification on by default?

To prevent man-in-the-middle attacks. If you connect to `https://vault:8200`, Initium verifies the server's TLS certificate against the system CA bundle.

If you need to connect to a service with a self-signed certificate (common in dev/staging), explicitly opt in:

```yaml
args: ["wait-for", "--target", "https://vault:8200/v1/sys/health", "--insecure-tls"]
```

The `--insecure-tls` flag is intentionally verbose — it should stand out in code review.

### Why does the image use `FROM scratch` instead of Alpine or Debian?

A `scratch` base image contains zero OS packages, zero libraries, zero shells. This means:

- **Zero CVEs** from base image packages — nothing to scan, nothing to patch
- **No shell** for attackers to use if the container is compromised
- **Tiny image** — the final image is ~2MB (just the Rust binary + CA certificates)

The trade-off is that you cannot `kubectl exec` into the container for debugging. This is acceptable for initContainers, which run once and exit.

### How do I verify that the Initium image hasn't been tampered with?

Release images include SBOM and provenance attestations. Verify with cosign:

```bash
cosign verify-attestation \
  --type https://slsa.dev/provenance/v0.2 \
  ghcr.io/kitstream/initium:0.1.0
```

### Is Initium safe to use in multi-tenant clusters?

Yes. Initium runs with least privilege, makes no cluster API calls, and cannot access other namespaces, nodes, or the Kubernetes API. It only makes outbound TCP/HTTP connections to targets you explicitly configure.

---

## Deployment

### How do I install Initium?

There are three ways:

**1. Reference the image directly in your pod spec** (simplest):

```yaml
initContainers:
  - name: wait-for-db
    image: ghcr.io/kitstream/initium:latest
    args: ["wait-for", "--target", "tcp://postgres:5432"]
```

**2. Use the Helm chart** (for templated deployments):

```bash
helm install my-app charts/initium \
  --set sampleDeployment.enabled=true \
  --set 'initContainers[0].name=wait-db' \
  --set 'initContainers[0].command[0]=wait-for' \
  --set 'initContainers[0].args[0]=--target' \
  --set 'initContainers[0].args[1]=tcp://postgres:5432'
```

**3. Build from source**:

```bash
git clone https://github.com/KitStream/initium.git
cd initium
make build
./bin/initium --help
```

### What image tag should I use?

- **`latest`** — tracks the most recent release. Convenient but not reproducible.
- **`0.1.0`** (specific version) — pinned and reproducible. **Recommended for production.**

Always pin a specific version in production workloads to avoid unexpected behavior from image updates.

### Does the Helm chart install any cluster resources?

No. The Helm chart installs nothing by default (`sampleDeployment.enabled: false`). It provides templates and values for injecting Initium initContainers into your own deployments. It does not create CRDs, webhooks, ClusterRoles, or any cluster-scoped resources.

### How do I share data between the initContainer and my main container?

Use an `emptyDir` volume mounted at `--workdir` (default `/work`):

```yaml
spec:
  initContainers:
    - name: render-config
      image: ghcr.io/kitstream/initium:latest
      args: ["render", "--template", "/templates/app.conf.tmpl", "--output", "app.conf", "--workdir", "/work"]
      volumeMounts:
        - name: workdir
          mountPath: /work
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: workdir
          mountPath: /work
          readOnly: true
  volumes:
    - name: workdir
      emptyDir: {}
```

The initContainer writes to `/work`, and the main container reads from it.

### How do I chain multiple init steps (wait → migrate → seed)?

Define multiple initContainers in order. Kubernetes runs them sequentially:

```yaml
initContainers:
  - name: wait-for-db
    image: ghcr.io/kitstream/initium:latest
    args: ["wait-for", "--target", "tcp://postgres:5432"]
    securityContext: &initium-security
      runAsNonRoot: true
      runAsUser: 65534
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: [ALL]

  - name: migrate
    image: ghcr.io/kitstream/initium:latest
    args: ["migrate", "--", "/app/migrate", "up"]
    securityContext: *initium-security

  - name: seed
    image: ghcr.io/kitstream/initium:latest
    args: ["seed", "--", "/app/seed", "--file", "/seeds/data.sql"]
    securityContext: *initium-security
```

If any step fails, the subsequent steps do not run and the pod stays in `Init:Error`.

### What Kubernetes versions does Initium support?

Initium has no dependency on the Kubernetes API — it is just a binary that runs inside a container. It works on any Kubernetes version that supports `initContainers` (1.6+). The Helm chart uses standard `apps/v1` APIs and works on Kubernetes 1.16+.

### Can I use Initium with a private container registry?

Yes. Pull the public image and push it to your registry:

```bash
docker pull ghcr.io/kitstream/initium:0.1.0
docker tag ghcr.io/kitstream/initium:0.1.0 registry.internal/initium:0.1.0
docker push registry.internal/initium:0.1.0
```

Then reference your internal registry in the pod spec. If your registry requires authentication, configure an `imagePullSecret` as usual.

### How do I build Initium locally?

```bash
git clone https://github.com/KitStream/initium.git
cd initium
make build        # produces target/release/initium
make test         # runs all unit tests
make lint         # runs cargo clippy + cargo fmt --check
```

### How do I build a custom Docker image?

```bash
# Build for your local architecture
docker build -t initium:dev .

# Build multi-arch (requires Docker Buildx)
make docker-build VERSION=dev
```

### My initContainer is stuck in `Init:CrashLoopBackOff`. How do I debug?

Check the initContainer logs:

```bash
kubectl logs <pod-name> -c <initcontainer-name>
```

Common causes:

| Symptom                                   | Likely cause                                 | Fix                                                                          |
| ----------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
| `target not reachable` after all attempts | Target service isn't running or DNS is wrong | Check the service/endpoint exists and is in the same namespace (or use FQDN) |
| `unsupported target scheme`               | Missing `tcp://` or `http://` prefix         | Add the scheme: `tcp://postgres:5432` not just `postgres:5432`               |
| `path traversal detected`                 | Output path tries to escape workdir          | Use a relative path for `--output`                                           |
| `context cancelled`                       | Overall `--timeout` was too short            | Increase `--timeout` or check why the target takes so long                   |

### Does Initium support ARM-based nodes (e.g., AWS Graviton)?

Yes. The container image is built for both `linux/amd64` and `linux/arm64`. Kubernetes pulls the correct architecture automatically via the multi-arch manifest.