# How oblien/openship Manages Secret Keys and Sensitive Information

> Learn how oblien/openship secures sensitive info by storing secret keys in environment variables, validating with Zod, and injecting them into memory once without logging or disk persistence.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: how-to-guide
- Published: 2026-07-29

---

**oblien/openship stores all secret keys and sensitive configuration in environment variables, validates them with Zod schemas at startup, and injects critical secrets like `BETTER_AUTH_SECRET` into memory only once without ever logging or persisting them to disk.**

The oblien/openship repository follows the twelve-factor app methodology for configuration management, ensuring that credentials, API keys, and encryption secrets remain external to the codebase. By leveraging environment variables validated through strict schemas, the application maintains security across development and production environments without hard-coding sensitive values.

## Environment Variable Architecture

Openship adopts the classic twelve-factor approach of storing configuration in environment variables, keeping secrets out of source control. The repository provides an **`.env.example`** file at the root that serves as a comprehensive template, listing every variable the runtime expects—from database credentials and API keys to internal encryption secrets like `BETTER_AUTH_SECRET`.

Developers copy this template to a local `.env` file (which is explicitly excluded from Git via `.gitignore`) and populate it with secure values. This ensures that production secrets never appear in commit history while maintaining clear documentation of required configuration.

### Database Credentials from Environment

Database connection parameters are resolved directly from `process.env` at module load time. In **[`packages/db/src/client.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/client.ts)**, the client constructs connection strings using standard PostgreSQL environment variables:

```typescript
const host = process.env.POSTGRES_HOST ?? process.env.PGHOST;
const user = process.env.POSTGRES_USER ?? process.env.PGUSER ?? "openship";
const password = process.env.POSTGRES_PASSWORD ?? process.env.PGPASSWORD;
const db = process.env.POSTGRES_DB ?? process.env.PGDATABASE ?? user;

```

This pattern ensures that database passwords and host information remain external to the codebase, allowing the same container image to run against different databases by changing environment variables alone.

## Configuration Validation with Zod

To prevent runtime errors from missing or malformed secrets, openship validates all environment variables at application startup using a Zod schema defined in **[`apps/api/src/config/env.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/config/env.ts)**. This creates a single source of truth for configuration types and default values.

The schema includes strict typing for sensitive fields while providing safe defaults for development:

```typescript
import { z } from "zod";

const DEFAULT_BETTER_AUTH_SECRET = "change-me-in-production";

export const env = z.object({
  DATABASE_URL: z.string().default("postgres://postgres:postgres@localhost:5432/openship"),
  OBLIEN_CLIENT_ID: z.string().optional(),
  OBLIEN_CLIENT_SECRET: z.string().optional(),
  BETTER_AUTH_SECRET: z.string().default(DEFAULT_BETTER_AUTH_SECRET),
  // … additional variables …
}).parse(process.env);

```

When the application boots, `z.parse()` throws an error if required secrets are missing or invalid, failing fast before the server accepts traffic. The validated `env` object is then imported throughout the codebase, ensuring type-safe access to sensitive configuration.

## Runtime Secret Injection

Critical internal secrets require special handling to prevent exposure in logs or stack traces. Openship implements an injection pattern for **`BETTER_AUTH_SECRET`**, the primary key used for encrypting cookies, backup credentials, and other persisted data.

### The Injection Pattern

Located in **[`packages/adapters/src/backup/common/credentials.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/backup/common/credentials.ts)**, the `injectBetterAuthSecret` function resolves the secret from the environment exactly once at application boot:

```typescript
/** Inject the API‑resolved BETTER_AUTH_SECRET. Call once at app boot. */
export const injectBetterAuthSecret = (injectedSecret?: string) => {
  const secret = injectedSecret ?? process.env.BETTER_AUTH_SECRET;
  if (!secret) {
    throw new Error("BETTER_AUTH_SECRET is not set — cannot decrypt backup destination credentials.");
  }
  return secret;
};

```

This design ensures that the encryption key resides only in memory after startup, never appearing in error messages, logs, or serialized state. The function throws immediately if the secret is unavailable, preventing the application from running in an insecure configuration.

### Encryption at Rest

The `BETTER_AUTH_SECRET` derived from this injection pattern powers the application's encryption utilities. For example, in **[`apps/api/src/lib/encryption.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/lib/encryption.ts)**, the secret initializes AES-256-GCM ciphers for data protection:

```typescript
import { env } from "./config/env";

export const encrypt = (plain: string) => {
  const cipher = crypto.createCipheriv("aes-256-gcm", hashKey(env.BETTER_AUTH_SECRET), iv);
  // … encryption logic …
};

```

## Docker Deployment Security

When generating Docker Compose configurations, the CLI preserves existing secret values using the `keepSecret` helper in **[`apps/cli/src/lib/compose.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/lib/compose.ts)**. This function ensures that sensitive variables are passed through to containers using Docker's environment variable syntax without exposing them in generated files:

```typescript
function keepSecret(prev: Record<string, string>, key: string): string {
  return prev[key] ? `$${key}` : "";
}

// Usage in generated service definition:
`BETTER_AUTH_SECRET=${keepSecret(prev, "BETTER_AUTH_SECRET")}`

```

This approach allows the compose generator to update configuration files without overwriting production secrets, maintaining the security boundary between version-controlled templates and runtime configuration.

## Summary

- **External Configuration**: All secret keys and sensitive information live in environment variables, not source code, following the `.env.example` template.
- **Schema Validation**: The Zod schema in [`apps/api/src/config/env.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/config/env.ts) validates secrets at startup, throwing errors for missing required values.
- **Memory-Only Secrets**: Critical keys like `BETTER_AUTH_SECRET` are injected once at boot and stored only in runtime memory, never logged or persisted.
- **Database Security**: Connection credentials are read from standard PostgreSQL environment variables in [`packages/db/src/client.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/client.ts) without hard-coded defaults.
- **Deployment Safety**: The CLI's `keepSecret` helper ensures Docker Compose files reference secrets without exposing their values.

## Frequently Asked Questions

### Where are secret keys stored in oblien/openship?

Secret keys are stored exclusively in environment variables defined in a local `.env` file, which is never committed to Git. The repository only contains `.env.example`, a template documenting all required variables including `BETTER_AUTH_SECRET`, database passwords, and third-party API keys.

### How does openship prevent missing secret errors in production?

The application validates all environment variables at startup using a Zod schema in [`apps/api/src/config/env.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/config/env.ts). If required secrets are missing or malformed, `z.parse()` throws an immediate error, preventing the server from starting in an insecure state.

### What is the BETTER_AUTH_SECRET used for?

`BETTER_AUTH_SECRET` is the primary encryption key for the Better Auth library, used to encrypt cookies, backup destination credentials, and other sensitive persisted data. It is injected via `injectBetterAuthSecret()` at application boot and held only in memory.

### Are secrets exposed in Docker Compose files?

No. The CLI generator in [`apps/cli/src/lib/compose.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/lib/compose.ts) uses the `keepSecret()` helper to reference secrets using Docker's `$VARIABLE` syntax, ensuring that actual values are pulled from the host environment at runtime rather than written into generated YAML files.