@sprout-idws/cli
v1.2.0
Published
Sprout CLI: Docker Compose x-sprout, namespaces, encrypted variables, and Swarm deploy
Maintainers
Readme
Sprout CLI (@sprout-idws/cli)
Part of the Sprout monorepo, which also publishes NestJS libraries under @sprout-idws/sprout-*.
A CLI tool for managing Docker Swarm deployments with namespace isolation, encrypted configuration management, automated builds, and monitoring integration.
Sprout extends Docker Compose with a declarative configuration layer (x-sprout) that handles variable management, service routing, secret encryption, and zero-downtime deployments -- all from your existing compose files.
Table of Contents
- Installation
- Quick Start
- Concepts
- Compose File Configuration
- CLI Reference
- Real-World Examples
- Variable Flattening
- How Configs & Secrets Are Mounted
- Build & Versioning
- Deployment Pipeline
- Red-Green (Zero-Downtime) Deployments
- Monitoring & Alerting
- Directory Structure
- Development
Installation
# Clone the repository and navigate into it
cd sprout
# Install dependencies
npm install
# Install globally (adds `sprout` to your PATH)
./scripts/install.shThe install script compiles the TypeScript source, creates an executable at ~/.local/bin/sprout, and adds it to your shell PATH.
Update
./scripts/update.shUninstall
./scripts/uninstall.shQuick Start
# 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 deployConcepts
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 # optionalDefault 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.jsonmust define a valid npmname. That value is passed toturbo build --filter=...andturbo prune ... --dockerin the backend template.- Frontend only: commit
apps/<app>/nginx.confnext tocompose.yml(same pattern as a typical Vite + nginx setup). The default generated Dockerfile declaresARG BACKEND_URLand setsVITE_BACKEND_URL; add your own Dockerfile if you need more build-timeARGs.
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.
# 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_CASEkeys (see 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.
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: allFile-based health check (workers / jobs)
For services that report liveness by touching a file on a schedule, declare x-sprout.healthcheck with type: file. Sprout injects the full Docker healthcheck into the generated compose override, including a freshness rule: the file must exist and its modification time must be within twice the configured interval (matching Docker’s usual “heartbeat vs. interval” expectation).
You do not need to duplicate healthcheck: on the service for Sprout deploys—the override merges with your compose file.
x-sprout:
service:
type: backend
proxy:
type: private
healthcheck:
type: file
file: /tmp/apps/backend/worker/health
interval: 30s # optional; default 30s
timeout: 3s # optional; default 3s
retries: 3 # optional; default 3
start_period: 30s # optional; default 30sSupported duration suffixes on interval, timeout, and start_period: s, m, h.
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 |
x-sprout:
service:
type: backend # backend | frontend | monolith | infra | proxyIf 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):
x-sprout:
proxy:
type: public
domain: "api.example.com"
port: 5000Private service (internal network only):
x-sprout:
proxy:
type: privateWhen 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:
x-sprout:
configs:
inherit: all
secrets:
inherit: allThe service receives every config and secret defined at the app level.
Inherit by group name:
x-sprout:
configs:
- database
- redis
secrets:
- databaseGroup-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:
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 passwordThe 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
Namespace Commands
# Configure a new namespace (interactive prompt for project path)
sprout namespaces configure <name>
# List all configured namespaces
sprout namespaces list
# Delete a namespace
sprout namespaces delete <name>
# Set a namespace as the default
sprout namespaces use <name>
# Show the current default namespace
sprout namespaces use show
# Clear the default namespace
sprout namespaces use clearExample: Setting up a namespace
$ 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
production /home/user/projects/my-app (default)App Commands
# Build Docker images for all apps (or filtered)
sprout apps build [--namespace <ns>] [--no-cache] [--apps <app1,app2>]
# Deploy the entire stack via Docker Swarm
sprout apps deploy [--namespace <ns>] [--build] [--no-cache] [--apps <app1,app2>]
# Refresh (restart) a specific app or service
sprout apps refresh <app> [service] [--namespace <ns>]
# Regenerate deployment artifacts (compose overrides, alerts)
sprout apps generate artifacts [--namespace <ns>]Example: Building and deploying
# 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
# Refresh a single service after a config change
$ sprout apps refresh backend apiVariable Commands
# Set a variable for an app
sprout apps variables set <app> <key> <value> [namespace]
# Get a variable value
sprout apps variables get <app> <key> [namespace]
# List all variables interactively
sprout apps variables list [namespace]
# Remove a variable
sprout apps variables rm <app> <key> [namespace]Example: Managing variables
# 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_KEYVariables can also be set with an explicit namespace:
sprout apps variables set backend DATABASE_HOST 10.0.2.200 stagingThe password for encrypted storage is prompted interactively or can be set via the SPROUT_CONFIG_PASSWORD environment variable:
export SPROUT_CONFIG_PASSWORD=my-encryption-key
sprout apps variables set backend DATABASE_HOST 10.0.1.100Real-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
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
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_urlandbase_domain
Infrastructure Services
Infrastructure services (databases, caches) use group-based inheritance to only receive the variables they need.
apps/backend/compose-infra.yml
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
databaseconfigs and secrets (not Redis or JWT variables) - redis only receives
redisconfigs and secrets - Services read mounted files directly:
cat /run/secrets/REDIS_PASSWORD - Postgres uses Docker's native
*_FILEconvention:POSTGRES_USER_FILE: /run/configs/DATABASE_USER
Service with Basic Auth
Protect a public service with HTTP basic authentication:
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:
- redisThe username and password reference variable keys, not literal values. You set the actual credentials via the CLI:
sprout apps variables set backend REDIS_INSIGHT_USER admin
sprout apps variables set backend REDIS_INSIGHT_PASSWORD s3cur3-p@ssSprout 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):
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):
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:
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):
environment:
POSTGRES_USER_FILE: /run/configs/DATABASE_USER
POSTGRES_PASSWORD_FILE: /run/secrets/DATABASE_PASSWORDLocal development
For local development, override the mount paths to point to local directories:
# .env
CONFIGS_BASE_PATH=/home/user/configs
SECRETS_BASE_PATH=/home/user/secretsThen 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.
sprout apps buildWhat happens:
- Verifies the project is a Git repository
- Gets the current commit SHA (e.g.,
a1b2c3d) - Resolves a Dockerfile for each app: on-disk file if present, otherwise the generated default under
.sprout/docker/<app>/(see Default Dockerfiles above) - Builds each app image in parallel (
docker buildx, context = monorepo root) - Tags images as
<namespace>/<app>:<sha>and<namespace>/<app>:latest - Passes sprout variables as Docker build args (for
ARGdirectives in Dockerfiles) - Tracks the last 3 versions per app at
~/.sprout/namespaces/<ns>/apps/versions/<app>.env
Version file format:
latest=a1b2c3d
second_latest=f4e5d6c
third_latest=b7a8c9dThe ${<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:
# 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 stagingDeployment 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:
# Deploy with auto-build
sprout apps deploy --build
# Deploy without cache (forces rebuild)
sprout apps deploy --build --no-cache
# Deploy specific apps
sprout apps deploy --apps backend
# Deploy for a specific namespace
sprout apps deploy --namespace productionRed-Green (Zero-Downtime) Deployments
Sprout automatically enables red-green (rolling) deployments for services that meet both conditions:
- The service has a
healthcheckdefined - The service has
replicas > 1
When both conditions are met, the generated compose override includes:
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: 120sThis 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.ymlInfrastructure files (~/.local/share/sprout/)
~/.local/share/sprout/
└── infrastructure/
├── compose-infra.yml
├── config/
│ ├── grafana/
│ ├── loki/
│ ├── prometheus/
│ └── promtail/
└── deploy/
├── config/
└── alerts/ # Generated alert rulesDevelopment
Prerequisites
- Node.js 20+
- Docker with Swarm mode enabled (
docker swarm init) - Git
Setup
npm installScripts
# 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 lintTech 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
