npm package: @sprout-idws/cli
Description: A TypeScript command line tool built with Commander.js
Author: Sprout
Latest version: 1.1.0

# Sprout CLI (`@sprout-idws/cli`)

Part of the [Sprout monorepo](../README.md), which also publishes NestJS libraries under `@sprout-idws/sprout-*`.

## Why Sprout exists

Sprout is the operational layer for **Docker Swarm** stacks that are described with **Docker Compose**. Teams keep a single source of truth in compose files; Sprout adds a small, explicit extension (`x-sprout`) so you do not hand-maintain Traefik labels, config/secret mounts, image tags, or Grafana alert stubs in parallel. Each **namespace** ties the CLI to one project directory and one environment (for example `staging` vs `production`). **Configs and secrets** from your schema are stored **encrypted** locally, then synced into Docker configs and secrets at deploy time.

The CLI exists to do that work end to end: **point at a project**, **set variables**, **build** tagged images from Git, **deploy** the full stack, **refresh** individual services when needed, and **regenerate** compose overrides and monitoring artifacts when definitions change—without putting secrets in Git.

Sprout extends Docker Compose with a declarative configuration layer (`x-sprout`) that handles variable management, service routing, secret handling, and optional zero-downtime deployment settings—all from your existing compose files.

---

## Table of Contents

- [Why Sprout exists](#why-sprout-exists)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Command overview](#command-overview)
- [Concepts](#concepts)
  - [Namespaces](#namespaces)
  - [Apps](#apps)
  - [Variables (Configs & Secrets)](#variables-configs--secrets)
  - [The `x-sprout` Extension](#the-x-sprout-extension)
- [Compose File Configuration](#compose-file-configuration)
  - [App-Level Configuration](#app-level-configuration)
  - [Service-Level Configuration](#service-level-configuration)
  - [Service Types](#service-types)
  - [Proxy Configuration](#proxy-configuration)
  - [Config & Secret Inheritance](#config--secret-inheritance)
  - [Basic Authentication](#basic-authentication)
- [CLI Reference](#cli-reference)
  - [Global options](#global-and-shared-options)
  - [Top-level commands](#top-level-commands)
  - [Namespaces](#namespace-commands)
  - [Apps](#app-commands)
  - [Variables](#variable-commands)
  - [Shell completion](#shell-completion)
- [Real-World Examples](#real-world-examples)
  - [Backend API with Database & Redis](#backend-api-with-database--redis)
  - [Frontend Application](#frontend-application)
  - [Infrastructure Services](#infrastructure-services)
  - [Service with Basic Auth](#service-with-basic-auth)
- [Variable Flattening](#variable-flattening)
- [How Configs & Secrets Are Mounted](#how-configs--secrets-are-mounted)
- [Build & Versioning](#build--versioning)
- [Deployment Pipeline](#deployment-pipeline)
- [Red-Green (Zero-Downtime) Deployments](#red-green-zero-downtime-deployments)
- [Monitoring & Alerting](#monitoring--alerting)
- [Directory Structure](#directory-structure)
- [Development](#development)

---

## Installation

```bash
# From the monorepo: enter the CLI package (this directory)
cd sprout/cli

# Install dependencies
npm install

# Install globally (adds `sprout` to your PATH)
./scripts/install.sh
```

The install script compiles the TypeScript source, creates an executable at `~/.local/bin/sprout`, and adds it to your shell PATH.

### Update

Run from this package directory (`sprout/cli`):

```bash
./scripts/update.sh
```

### Uninstall

```bash
./scripts/uninstall.sh
```

---

## Quick Start

```bash
# 1. Configure a namespace (links sprout to your project)
sprout namespaces configure production

# 2. Set the namespace as default
sprout namespaces use production

# 3. Set configuration variables for an app
sprout apps variables set backend DATABASE_HOST 10.0.1.5
sprout apps variables set backend DATABASE_PORT 5432
sprout apps variables set backend DATABASE_PASSWORD my-secret-pw

# 4. Build Docker images
sprout apps build

# 5. Deploy the entire stack
sprout apps deploy
```

---

## Command overview

| Area | Command | What it does |
|------|---------|----------------|
| Top-level | `sprout help` | Print a short summary of commands (same spirit as `sprout --help`) |
| Top-level | `sprout config [namespace]` | Shortcut for `sprout namespaces configure` |
| Top-level | `sprout completion bash \| zsh` | Print a shell completion script |
| Namespaces | `sprout namespaces configure [namespace]` | Interactive setup: link namespace to project path |
| Namespaces | `sprout namespaces list` | List configured namespace names |
| Namespaces | `sprout namespaces delete <name>` | Remove a namespace (`-f` / `--force` skips confirmation) |
| Namespaces | `sprout namespaces use <name>` | Set default namespace |
| Namespaces | `sprout namespaces use show` | Show current default |
| Namespaces | `sprout namespaces use clear` | Clear default |
| Apps | `sprout apps build` | Build Docker images for discovered apps |
| Apps | `sprout apps deploy` | Sync variables, generate artifacts, `docker stack deploy` |
| Apps | `sprout apps deploy --build` | Build (optionally filtered) then deploy |
| Apps | `sprout apps refresh <app> [service]` | Rolling restart for an app or one service |
| Apps | `sprout apps generate artifacts` | Regenerate compose overrides and Grafana alerts only |
| Variables | `sprout apps variables set \| get \| list \| rm` | Encrypted variable store per app |

Discovery rules: each **app** is a directory under `<project>/apps/` that contains compose files (`compose.yml`, `compose.yaml`, `docker-compose.yml`, `docker-compose.yaml`, plus `compose-infra.yml` / `compose-infra.yaml` beside them when present). Sprout also loads a synthetic **`infrastructure`** app from `compose-infra.yml` in the [infrastructure directory](#directory-structure) (after install, usually `~/.local/share/sprout/infrastructure/`).

For flags and examples, see [CLI Reference](#cli-reference). Use `sprout <command> --help` for exact options.

---

## Concepts

### Namespaces

A **namespace** is an isolated deployment environment tied to a project directory. Each namespace has its own set of encrypted variables, build versions, and compose overrides. This allows you to deploy the same project to multiple environments (e.g., `staging`, `production`) with different configurations.

Namespace data is stored at `~/.sprout/namespaces/<name>/`.

### Apps

An **app** is a directory inside your project's `apps/` folder that contains one or more Docker Compose files. Sprout automatically discovers apps by scanning for compose files (`compose.yml`, `compose.yaml`, `docker-compose.yml`, `docker-compose.yaml`, `compose-infra.yml`, `compose-infra.yaml`).

```
my-project/
├── apps/
│   ├── backend/          # App: "backend"
│   │   ├── compose.yml
│   │   ├── compose-infra.yml
│   │   └── Dockerfile   # optional — see default Dockerfiles below
│   └── ui/               # App: "ui"
│       ├── compose.yml
│       ├── nginx.conf    # required when using the generated frontend image
│       └── Dockerfile    # optional
```

### Default Dockerfiles (generated)

If there is **no** Dockerfile in the app directory (Sprout also checks `docker/Dockerfile` and a few other conventional paths), Sprout **generates** one under **`.sprout/docker/<app>/Dockerfile`** when it loads the app (for example before `sprout apps build`). The template is chosen from your compose services:

| Condition | Generated image |
|---|---|
| Every service with `type: backend` or `type: frontend` is **backend** (others ignored) | **Backend**: Turbo prune/build, Prisma generate, production `node_modules`, `dumb-init` + entrypoint that loads `/run/configs` and `/run/secrets`, waits for DB/Redis when host/port envs are set, runs `prisma migrate deploy` (unless `SKIP_PRISMA_MIGRATE=true`), then `node ./dist/$SERVICE_TYPE/$SERVICE_TYPE.js`. |
| Same, but every such service is **frontend** | **Frontend**: Turbo build for the app package, **nginx** serving static files (same idea as a Vite `dist` + nginx image). |
| No `backend`/`frontend` services, or a **mix** of backend and frontend among those | **Error** — add your own `Dockerfile` under the app, or align `x-sprout.service.type` for backend vs frontend services. |

**Requirements**

- **`apps/<app>/package.json`** must define a valid npm **`name`**. That value is passed to `turbo build --filter=...` and `turbo prune ... --docker` in the backend template.
- **Frontend only:** commit **`apps/<app>/nginx.conf`** next to `compose.yml` (same pattern as a typical Vite + nginx setup). The default generated Dockerfile declares **`ARG BACKEND_URL`** and sets **`VITE_BACKEND_URL`**; add your own Dockerfile if you need more build-time `ARG`s.

**Ignored for this choice:** `infra`, **`monolith`**, **`proxy`**, and any other type that is not `backend` or `frontend` (they usually use their own `image:` in compose, e.g. RedisInsight beside your API). Pure **infra** compose (only postgres/redis, etc.) still needs an explicit Dockerfile if you intend to **build** that app folder as a single image.

### Variables (Configs & Secrets)

Sprout manages two types of variables:

- **Configs** -- non-sensitive configuration values (database hosts, ports, feature flags)
- **Secrets** -- sensitive values (passwords, API keys, tokens)

All variables are stored **encrypted** on disk using AES-256-GCM with PBKDF2 key derivation. At deploy time, they are synced to Docker configs and secrets, then mounted into containers as files.

| Aspect | Config | Secret |
|---|---|---|
| Storage | Encrypted on disk | Encrypted on disk |
| Docker primitive | Docker Config | Docker Secret |
| Mount path | `/run/configs/<KEY>` | `/run/secrets/<KEY>` |
| Use case | Hosts, ports, URLs | Passwords, tokens, keys |

### The `x-sprout` Extension

Sprout uses Docker Compose's extension fields (`x-sprout`) to declaratively define application configuration, service types, proxy routing, and variable inheritance -- all inside your compose files. No separate configuration files are needed.

---

## Compose File Configuration

### App-Level Configuration

Define configs and secrets at the top level of your compose file using `x-sprout`. These become the variable schema for the app, with optional default values.

```yaml
# compose.yml
version: "3.8"

x-sprout:
  configs:
    base_domain: ""
    database:
      user: "postgres"
      host: "postgres"
      port: "5432"
    redis:
      host: "redis"
      port: "6379"
    jwt:
      expires_in: "1h"

  secrets:
    database:
      password: ""
    redis:
      password: ""
    jwt_secret: ""
```

**Key points:**

- Nested objects are **flattened** to `UPPER_SNAKE_CASE` keys (see [Variable Flattening](#variable-flattening))
- Empty string `""` means no default -- the value must be set via the CLI
- Non-empty strings serve as defaults if no value is explicitly set

### Service-Level Configuration

Each service can have its own `x-sprout` block to define its type, proxy settings, and which configs/secrets it needs.

```yaml
services:
  api:
    image: ${NAMESPACE}/backend:${BACKEND_LATEST_TAG}

    x-sprout:
      service:
        type: backend

      proxy:
        domain: "api.${BASE_DOMAIN}"
        type: public
        port: 5000

      configs:
        inherit: all
      secrets:
        inherit: all
```

### Service Types

| Type | Value | Description |
|---|---|---|
| Backend | `backend` | Backend API or worker service |
| Frontend | `frontend` | UI / single-page application |
| Monolith | `monolith` | Self-contained application (e.g., RedisInsight, pgAdmin) |
| Infrastructure | `infra` | Infrastructure service (databases, caches) |
| Proxy | `proxy` | Reverse proxy / load balancer |

```yaml
x-sprout:
  service:
    type: backend   # backend | frontend | monolith | infra | proxy
```

If no type is specified, the service defaults to `infra`.

### Proxy Configuration

The proxy configuration determines how the service is exposed to the network via Traefik.

**Public service** (accessible from the internet):

```yaml
x-sprout:
  proxy:
    type: public
    domain: "api.example.com"
    port: 5000
```

**Private service** (internal network only):

```yaml
x-sprout:
  proxy:
    type: private
```

When `type: public`, sprout generates Traefik labels in the compose override:

- `traefik.http.routers.<service>.rule=Host(<domain>)`
- `traefik.http.services.<service>.loadbalancer.server.port=<port>`
- TLS with automatic certificate resolution
- WebSocket-secure entrypoint

### Config & Secret Inheritance

Services can inherit configs and secrets from the app-level `x-sprout` definition in three ways:

**Inherit all:**

```yaml
x-sprout:
  configs:
    inherit: all
  secrets:
    inherit: all
```

The service receives every config and secret defined at the app level.

**Inherit by group name:**

```yaml
x-sprout:
  configs:
    - database
    - redis
  secrets:
    - database
```

Group-based inheritance uses the top-level key names. For example, inheriting `database` gives the service all keys under the `database` group: `DATABASE_USER`, `DATABASE_HOST`, `DATABASE_PORT`, etc.

**No inheritance (default):**

If no `configs` or `secrets` key is provided, the service receives no variables.

### Basic Authentication

Public services can be protected with HTTP basic authentication:

```yaml
x-sprout:
  proxy:
    domain: "admin.example.com"
    type: public
    port: 8080
    authentication:
      type: basic
      users:
        - username: ADMIN_USER        # Config key holding the username
          password: ADMIN_PASSWORD     # Secret key holding the password
```

The `username` and `password` fields reference variable **keys** (not literal values). Sprout reads the actual values from the encrypted variable store and generates bcrypt-hashed Traefik basic auth middleware.

---

## CLI Reference

### Global and shared options

Several commands need the **encryption password** for the variable store. You can:

- Set **`SPROUT_CONFIG_PASSWORD`** in the environment (non-interactive CI and scripts), or
- Pass **`--password <value>`** on the command, or
- Omit both and enter the password when prompted.

**Namespace selection:** Most `apps` subcommands accept **`-n` / `--namespace <name>`**. If omitted, Sprout uses the [default namespace](#namespace-commands) from `sprout namespaces use`.

### Top-level commands

```bash
sprout --help              # Full CLI help
sprout --version           # CLI version

sprout help                # Short overview of subcommands (same information as the built-in help text)

sprout config [namespace]  # Same as: sprout namespaces configure [namespace]
```

### Namespace Commands

```bash
# Configure a namespace (interactive prompt for project path; name optional if default exists)
sprout namespaces configure [name]

# List configured namespace names
sprout namespaces list

# Delete a namespace (prompts for confirmation unless forced)
sprout namespaces delete <name>
sprout namespaces delete <name> --force   # alias: -f

# Default namespace for other commands
sprout namespaces use <name>
sprout namespaces use show
sprout namespaces use clear
```

#### Example: Setting up a namespace

```bash
$ sprout namespaces configure production
? Project path: /home/user/projects/my-app

✅ Namespace 'production' configured successfully

$ sprout namespaces use production
✅ Default namespace set to 'production'

$ sprout namespaces list
📋 Configured namespaces:

  production

Total: 1 namespace(s)

$ sprout namespaces use show
📋 Current default namespace: production ✅
```

### App Commands

```bash
# Build Docker images for all apps (or a subset)
sprout apps build [-n <ns>] [--no-cache] [--apps app1,app2] [--password <pw>]

# Deploy the full stack (always all discovered compose files for the namespace)
sprout apps deploy [-n <ns>] [--password <pw>]

# Optional: build before deploy (--apps and --no-cache only apply together with --build)
sprout apps deploy [-n <ns>] --build [--no-cache] [--apps app1,app2] [--password <pw>]

# Rolling refresh (restart) for one app or a single service
sprout apps refresh <app> [service] [-n <ns>] [--password <pw>]

# Regenerate compose overrides and Grafana artifacts without deploying
sprout apps generate artifacts [-n <ns>] [--password <pw>]
```

#### Example: Building and deploying

```bash
# Build all apps
$ sprout apps build
🔨 Building 2 applications...
📝 Using commit SHA: a1b2c3d
✅ Successfully built and registered backend with commit a1b2c3d
✅ Successfully built and registered ui with commit a1b2c3d

# Deploy everything
$ sprout apps deploy
Deploying Docker stack 'production' with 5 compose files

# Build and deploy in one step
$ sprout apps deploy --build

# Build only specific apps
$ sprout apps build --apps backend,ui

# Build only some apps, then deploy the full stack
$ sprout apps deploy --build --apps backend,ui

# Refresh a single service after a config change
$ sprout apps refresh backend api
```

### Variable Commands

```bash
# Set a variable for an app (optional namespace as last argument)
sprout apps variables set <app> <key> <value> [namespace] [--password <pw>]

# Remove a variable
sprout apps variables rm <app> <key> [namespace] [--password <pw>]

# Get a variable value
sprout apps variables get <app> <key> [namespace] [--password <pw>]

# Interactive: pick an app, search variables, edit values (ESC / prompts to navigate)
sprout apps variables list [-n <ns>] [--password <pw>]
```

#### Example: Managing variables

```bash
# Set database configuration
$ sprout apps variables set backend DATABASE_HOST 10.0.1.100
$ sprout apps variables set backend DATABASE_PORT 5432
$ sprout apps variables set backend DATABASE_USER admin
$ sprout apps variables set backend DATABASE_PASSWORD s3cur3-p@ss!

# Verify a value
$ sprout apps variables get backend DATABASE_HOST
10.0.1.100

# Interactive listing (shows all variables, allows editing)
$ sprout apps variables list
? Select app: backend
  DATABASE_HOST = 10.0.1.100
  DATABASE_PORT = 5432
  DATABASE_USER = admin
  DATABASE_PASSWORD = ********
  REDIS_HOST = (not set)
  ...

# Remove a variable
$ sprout apps variables rm backend DEPRECATED_KEY
```

Variables can also be set with an explicit namespace:

```bash
sprout apps variables set backend DATABASE_HOST 10.0.2.200 staging
```

The password for encrypted storage is prompted interactively, or use **`SPROUT_CONFIG_PASSWORD`** or **`--password`** (see [Global and shared options](#global-and-shared-options)):

```bash
export SPROUT_CONFIG_PASSWORD=my-encryption-key
sprout apps variables set backend DATABASE_HOST 10.0.1.100
```

### Shell completion

Sprout can emit **bash** or **zsh** completion scripts. The CLI inspects `SHELL` when you omit the shell name.

```bash
# Preview or install bash completion
sprout completion bash
# Example install:
# sprout completion bash | sudo tee /etc/bash_completion.d/sprout

# Zsh: write a file and source it from ~/.zshrc
sprout completion zsh > ~/.sprout-completion.zsh
echo 'source ~/.sprout-completion.zsh' >> ~/.zshrc
```

Completion includes namespaces, apps, services (for `refresh`), variable keys, and common flags. Hidden helper commands (`sprout __completion-*`) are used by the script; you normally do not run them by hand.

---

## Real-World Examples

### Backend API with Database & Redis

This example shows a backend application with an API server, a background worker, a scheduled job runner, and infrastructure services -- all managed by sprout.

**`apps/backend/compose.yml`**

```yaml
version: "3.8"

x-environment: &environment
  NODE_ENV: production
  DATABASE_HOST: "${DATABASE_HOST}"
  DATABASE_PORT: ${DATABASE_PORT}
  REDIS_HOST: ${REDIS_HOST}
  REDIS_PORT: ${REDIS_PORT}

x-sprout:
  configs:
    base_domain: ""
    database:
      user: "postgres"
      host: "postgres"
      port: "5432"
      max_connections: "100"
      pool_connection_limit: "10"
      pool_timeout_seconds: "30"
      pool_connect_timeout_seconds: "10"
    redis:
      db: "0"
      host: "redis"
      port: "6379"
    jwt:
      expires_in: "1h"
      refresh_expires_in: "15d"
    cors_origin: ""

  secrets:
    database:
      password: ""
    redis:
      password: ""
    jwt_secret: ""
    encryption_secret: ""

services:
  api:
    image: ${NAMESPACE}/backend:${BACKEND_LATEST_TAG}
    environment:
      SERVICE_TYPE: api
      <<: *environment

    x-sprout:
      service:
        type: backend
      proxy:
        domain: "api.${BASE_DOMAIN}"
        type: public
        port: 5000
      configs:
        inherit: all
      secrets:
        inherit: all

    ports:
      - "5000:5000"

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]

    deploy:
      replicas: 2
      resources:
        limits:
          memory: 256M
          cpus: "0.2"

  worker:
    image: ${NAMESPACE}/backend:${BACKEND_LATEST_TAG}
    environment:
      SERVICE_TYPE: worker
      <<: *environment

    x-sprout:
      service:
        type: backend
      proxy:
        type: private
      configs:
        inherit: all
      secrets:
        inherit: all

    healthcheck:
      test: ["CMD-SHELL", "test -f /tmp/apps/backend/worker/health"]

    deploy:
      replicas: 1
      resources:
        limits:
          memory: 512M
          cpus: "0.2"

  job:
    image: ${NAMESPACE}/backend:${BACKEND_LATEST_TAG}
    environment:
      SERVICE_TYPE: job
      <<: *environment

    x-sprout:
      service:
        type: backend
      proxy:
        type: private
      configs:
        inherit: all
      secrets:
        inherit: all

    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
          cpus: "0.2"
```

In this example:

- The **api** service is publicly exposed at `api.<BASE_DOMAIN>` with 2 replicas for high availability
- The **worker** and **job** services are private (no external access)
- All three services inherit every config and secret defined at the app level
- The `${NAMESPACE}` and `${BACKEND_LATEST_TAG}` variables are automatically resolved by sprout at deploy time

### Frontend Application

**`apps/ui/compose.yml`**

```yaml
version: "3.8"

x-sprout:
  configs:
    backend_url: ""
    base_domain: ""

services:
  ui:
    image: ${NAMESPACE}/ui:${UI_LATEST_TAG}

    build:
      context: ../..
      dockerfile: apps/ui/Dockerfile
      args:
        VITE_BACKEND_URL: ${BACKEND_URL}

    x-sprout:
      service:
        type: frontend
      proxy:
        domain: "backoffice.${BASE_DOMAIN}"
        type: public
        port: 3000

    ports:
      - "3000:3000"

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]

    deploy:
      replicas: 2
      resources:
        limits:
          memory: 64M
          cpus: "0.2"
```

In this example:

- The UI is publicly accessible at `backoffice.<BASE_DOMAIN>`
- Build args (like `VITE_BACKEND_URL`) are resolved from sprout variables at build time
- The UI does not inherit backend secrets -- it only declares `backend_url` and `base_domain`

### Infrastructure Services

Infrastructure services (databases, caches) use group-based inheritance to only receive the variables they need.

**`apps/backend/compose-infra.yml`**

```yaml
version: "3.8"

x-sprout:
  configs:
    database:
      user: "postgres"
      host: "postgres"
      port: "5432"
      max_connections: "100"
    redis:
      db: "0"
      host: "redis"
      port: "6379"

  secrets:
    database:
      password: ""
    redis:
      password: ""

services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_DB: brasmaq-backoffice
      POSTGRES_USER_FILE: /run/configs/DATABASE_USER
      POSTGRES_PASSWORD_FILE: /run/secrets/DATABASE_PASSWORD
    command: ["postgres", "-c", "max_connections=${DATABASE_MAX_CONNECTIONS}"]

    x-sprout:
      configs:
        - database
      secrets:
        - database

    volumes:
      - postgres_data:/var/lib/postgresql/data

    deploy:
      resources:
        limits:
          memory: 256M

  redis:
    image: redis:7-alpine

    x-sprout:
      configs:
        - redis
      secrets:
        - redis

    command: |
      sh -c 'redis-server --requirepass "$$(cat /run/secrets/REDIS_PASSWORD)"'

    volumes:
      - redis_data:/data

  postgres-backup:
    image: kartoza/pg-backup:17-3.5
    environment:
      POSTGRES_DB: brasmaq-backoffice
      REMOVE_BEFORE: 30
      CRON_SCHEDULE: "0 2 * * *"
    entrypoint:
      - /bin/sh
      - -c
      - |
        export POSTGRES_HOST=$$(cat /run/configs/DATABASE_HOST)
        export POSTGRES_PORT=$$(cat /run/configs/DATABASE_PORT)
        export POSTGRES_USER=$$(cat /run/configs/DATABASE_USER)
        export POSTGRES_PASS=$$(cat /run/secrets/DATABASE_PASSWORD)
        exec /backup-scripts/start.sh

    x-sprout:
      configs:
        - database
      secrets:
        - database

volumes:
  postgres_data:
  redis_data:
```

In this example:

- **postgres** only receives `database` configs and secrets (not Redis or JWT variables)
- **redis** only receives `redis` configs and secrets
- Services read mounted files directly: `cat /run/secrets/REDIS_PASSWORD`
- Postgres uses Docker's native `*_FILE` convention: `POSTGRES_USER_FILE: /run/configs/DATABASE_USER`

### Service with Basic Auth

Protect a public service with HTTP basic authentication:

```yaml
services:
  redisinsight:
    image: redis/redisinsight:latest

    x-sprout:
      service:
        type: monolith
      proxy:
        domain: "redis-insight.${BASE_DOMAIN}"
        type: public
        port: 5540
        authentication:
          type: basic
          users:
            - username: REDIS_INSIGHT_USER
              password: REDIS_INSIGHT_PASSWORD
      configs:
        - redis
      secrets:
        - redis
```

The `username` and `password` reference **variable keys**, not literal values. You set the actual credentials via the CLI:

```bash
sprout apps variables set backend REDIS_INSIGHT_USER admin
sprout apps variables set backend REDIS_INSIGHT_PASSWORD s3cur3-p@ss
```

Sprout generates Traefik basic auth middleware labels in the compose override.

---

## Variable Flattening

Nested YAML objects in `x-sprout.configs` and `x-sprout.secrets` are automatically flattened to `UPPER_SNAKE_CASE` keys.

**Input (compose.yml):**

```yaml
x-sprout:
  configs:
    database:
      user: "postgres"
      host: "localhost"
      port: "5432"
    redis:
      host: "redis"
      port: "6379"
      insight:
        user: "redisinsight"
  secrets:
    database:
      password: ""
```

**Flattened result:**

| Original Path | Flattened Key | Type |
|---|---|---|
| `database.user` | `DATABASE_USER` | Config |
| `database.host` | `DATABASE_HOST` | Config |
| `database.port` | `DATABASE_PORT` | Config |
| `redis.host` | `REDIS_HOST` | Config |
| `redis.port` | `REDIS_PORT` | Config |
| `redis.insight.user` | `REDIS_INSIGHT_USER` | Config |
| `database.password` | `DATABASE_PASSWORD` | Secret |

These flattened keys are what you use in the CLI and what gets mounted in containers.

---

## How Configs & Secrets Are Mounted

When sprout deploys, it syncs variables to Docker configs and secrets. Each variable is stored as a Docker config or secret named `<KEY>_<HASH>` (where `HASH` is a short SHA-256 of the value, ensuring updates propagate correctly).

Inside the container, they are mounted as individual files:

```
/run/configs/DATABASE_HOST      → contains "10.0.1.100"
/run/configs/DATABASE_PORT      → contains "5432"
/run/configs/DATABASE_USER      → contains "postgres"
/run/secrets/DATABASE_PASSWORD  → contains "s3cur3-p@ss"
/run/secrets/JWT_SECRET         → contains "my-jwt-secret"
```

### Reading configs in your application

**Shell script (entrypoint):**

```bash
CONFIGS_BASE_PATH="${CONFIGS_BASE_PATH:-/run/configs}"
SECRETS_BASE_PATH="${SECRETS_BASE_PATH:-/run/secrets}"

# Load a config into an environment variable
export DATABASE_HOST=$(cat "$CONFIGS_BASE_PATH/DATABASE_HOST")
export DATABASE_PASSWORD=$(cat "$SECRETS_BASE_PATH/DATABASE_PASSWORD")
```

**Node.js / TypeScript:**

```typescript
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';

const CONFIGS_PATH = process.env.CONFIGS_BASE_PATH || '/run/configs';
const SECRETS_PATH = process.env.SECRETS_BASE_PATH || '/run/secrets';

function loadConfigFiles(): Record<string, string> {
  const config: Record<string, string> = {};

  for (const basePath of [CONFIGS_PATH, SECRETS_PATH]) {
    if (!existsSync(basePath)) continue;
    for (const file of readdirSync(basePath)) {
      config[file] = readFileSync(join(basePath, file), 'utf-8').trim();
    }
  }

  return config;
}

// Usage
const config = loadConfigFiles();
console.log(config.DATABASE_HOST);     // "10.0.1.100"
console.log(config.DATABASE_PASSWORD); // "s3cur3-p@ss"
```

**Docker native `_FILE` convention (e.g., PostgreSQL):**

```yaml
environment:
  POSTGRES_USER_FILE: /run/configs/DATABASE_USER
  POSTGRES_PASSWORD_FILE: /run/secrets/DATABASE_PASSWORD
```

### Local development

For local development, override the mount paths to point to local directories:

```bash
# .env
CONFIGS_BASE_PATH=/home/user/configs
SECRETS_BASE_PATH=/home/user/secrets
```

Then place plain text files in those directories with the variable names as filenames.

---

## Build & Versioning

Sprout builds Docker images using `docker buildx` and tags them with the current Git commit SHA.

```bash
sprout apps build
```

**What happens:**

1. Verifies the project is a Git repository
2. Gets the current commit SHA (e.g., `a1b2c3d`)
3. Resolves a Dockerfile for each app: on-disk file if present, otherwise the **generated** default under `.sprout/docker/<app>/` (see **Default Dockerfiles** above)
4. Builds each app image in parallel (`docker buildx`, context = monorepo root)
5. Tags images as `<namespace>/<app>:<sha>` and `<namespace>/<app>:latest`
6. Passes sprout variables as Docker build args (for `ARG` directives in Dockerfiles)
7. Tracks the last 3 versions per app at `~/.sprout/namespaces/<ns>/apps/versions/<app>.env`

**Version file format:**

```env
latest=a1b2c3d
second_latest=f4e5d6c
third_latest=b7a8c9d
```

The `${<APP>_LATEST_TAG}` variable (e.g., `${BACKEND_LATEST_TAG}`) is automatically resolved from this version file and injected into compose files at deploy time.

**Build options:**

```bash
# Skip Docker cache
sprout apps build --no-cache

# Build specific apps only
sprout apps build --apps backend,ui

# Build for a specific namespace
sprout apps build --namespace staging
```

---

## Deployment Pipeline

Running `sprout apps deploy` executes the following steps:

```
1. Resolve namespace & load encrypted variables
              │
2. Sync variables → Docker configs & secrets
   ├── Create new configs/secrets (KEY_HASH)
   └── Remove outdated configs/secrets
              │
3. Generate compose overrides
   ├── Traefik labels for public services
   ├── Config/secret mount declarations
   ├── Network assignments (proxy/backend)
   ├── Basic auth middleware
   ├── Red-green deployment config
   └── Logging configuration
              │
4. Generate monitoring artifacts
   ├── Grafana alert rules
   ├── Contact points
   └── Notification policies
              │
5. Deploy Docker Swarm stack
   └── docker stack deploy --compose-file <files...> <namespace>
```

The deployment merges your original compose files with the generated overrides, producing a complete stack definition that includes routing, secrets, monitoring, and deployment strategy.

**Deploy options:**

```bash
# Deploy with auto-build
sprout apps deploy --build

# Deploy without cache (forces rebuild)
sprout apps deploy --build --no-cache

# With --build only: limit which app images are rebuilt (deploy still applies the full stack)
sprout apps deploy --build --apps backend

# Use a specific namespace
sprout apps deploy --namespace production
```

---

## Red-Green (Zero-Downtime) Deployments

Sprout automatically enables red-green (rolling) deployments for services that meet both conditions:

1. The service has a `healthcheck` defined
2. The service has `replicas > 1`

When both conditions are met, the generated compose override includes:

```yaml
deploy:
  update_config:
    parallelism: 1
    delay: 5s
    failure_action: rollback
    monitor: 20s
    max_failure_ratio: 0.1
    order: start-first      # New container starts before old one stops
  rollback_config:
    parallelism: 1
    delay: 5s
    failure_action: pause
    monitor: 20s
    order: start-first
  restart_policy:
    condition: on-failure
    delay: 5s
    max_attempts: 3
    window: 120s
```

This ensures:

- New containers start and pass health checks before old ones are removed
- Automatic rollback if the new version fails health checks
- No downtime during deployments

---

## Monitoring & Alerting

Sprout automatically generates Grafana alert rules based on your service resource limits. Alert artifacts are created during deployment and placed in the infrastructure deploy directory.

**Generated alert types:**

| Alert | Source | Trigger |
|---|---|---|
| Container CPU limit | Prometheus | CPU usage exceeds container limit |
| Container memory limit | Prometheus | Memory usage exceeds container limit |
| Node CPU limit | Prometheus | Node CPU usage too high |
| Node memory limit | Prometheus | Node memory usage too high |
| Node disk limit | Prometheus | Node disk usage too high |
| Error log alerts | Loki | Error patterns in container logs |
| Container error log alerts | Loki | Container-level error logs |

**Severity levels:** `CRITICAL`, `SEVERE`, `WARNING`, `INFO`

The bundled infrastructure stack includes:

- **Prometheus** -- metrics collection
- **Grafana** -- dashboards and alerting
- **Loki** -- log aggregation
- **Promtail** -- log shipping

---

## Directory Structure

### Project layout (your project)

```
my-project/
├── apps/
│   ├── backend/
│   │   ├── compose.yml           # Main compose with x-sprout
│   │   ├── compose-infra.yml     # Infrastructure services
│   │   ├── Dockerfile
│   │   └── src/
│   └── ui/
│       ├── compose.yml
│       ├── Dockerfile
│       └── src/
```

### Sprout data directory (`~/.sprout/`)

```
~/.sprout/
├── using-namespace                    # Current default namespace
└── namespaces/
    └── production/
        ├── config.json                # Namespace config (project path)
        ├── apps/
        │   ├── configs/
        │   │   ├── backend.encrypted  # Encrypted variables for backend
        │   │   └── ui.encrypted       # Encrypted variables for ui
        │   └── versions/
        │       ├── backend.env        # Build version history
        │       └── ui.env
        └── compose-overrides/
            ├── backend/
            │   ├── compose-override.yml
            │   └── compose-infra-override.yml
            └── ui/
                └── compose-override.yml
```

### Infrastructure files (`~/.local/share/sprout/`)

```
~/.local/share/sprout/
└── infrastructure/
    ├── compose-infra.yml
    ├── config/
    │   ├── grafana/
    │   ├── loki/
    │   ├── prometheus/
    │   └── promtail/
    └── deploy/
        ├── config/
        └── alerts/          # Generated alert rules
```

---

## Development

### Prerequisites

- Node.js 20+
- Docker with Swarm mode enabled (`docker swarm init`)
- Git

### Setup

```bash
npm install
```

### Scripts

```bash
# Build the project
npm run build

# Run in development mode
npm run dev -- <command>

# Run tests
npm test
npm run test:watch
npm run test:cov

# Lint and format
npm run check
npm run check:fix
npm run format
npm run lint
```

### Tech Stack

- **TypeScript** 5.3 (target: ES2020, module: CommonJS)
- **Commander.js** -- CLI framework
- **Inquirer** -- Interactive prompts
- **js-yaml** -- YAML parsing
- **Handlebars** -- Template engine for compose overrides
- **Chalk** -- Terminal colors
- **Jest** -- Testing framework
- **Biome** -- Linting and formatting

---

## Security

- All variable values are encrypted at rest using **AES-256-GCM** with **PBKDF2** key derivation (100,000 iterations)
- Secrets are stored as Docker secrets (encrypted in the Swarm Raft log)
- The encryption password is never stored -- it must be provided interactively or via `SPROUT_CONFIG_PASSWORD`
- Docker configs/secrets use content-hash-based naming, so stale values are automatically cleaned up
- Basic auth passwords are bcrypt-hashed before being added to Traefik labels

---

## License

MIT