npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sprout-idws/cli

v1.2.0

Published

Sprout CLI: Docker Compose x-sprout, namespaces, encrypted variables, and Swarm deploy

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

# Clone the repository and navigate into it
cd sprout

# 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

./scripts/update.sh

Uninstall

./scripts/uninstall.sh

Quick 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 deploy

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 ARGs.

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_CASE keys (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: all

File-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 30s

Supported 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 | 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):

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

Private service (internal network only):

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:

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

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

Inherit by group name:

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:

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

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 clear

Example: 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 api

Variable 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_KEY

Variables can also be set with an explicit namespace:

sprout apps variables set backend DATABASE_HOST 10.0.2.200 staging

The 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.100

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

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_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

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:

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:

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):

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_PASSWORD

Local 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/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.

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:

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:

# 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:

# 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 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:

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

npm install

Scripts

# 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