Skip to content
NonetssPublic

About

Komodo CD — Self-hosted dashboard to manage and trigger deployments on Komodo stacks. Supports pull, redeploy and pull+redeploy actions via web UI or API key, making it easy to integrate with GitHub Actions or any CI/CD pipeline.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Repository files navigation

Komodo CD

A small continuous-deployment dashboard on top of Komodo: see the state of every stack, pull or redeploy them by hand, and trigger the same actions from CI with an API key.

Website and docs: nonetss.github.io/Komodo-CD

  • Overview: the key figures of the instance at a glance: stacks by state, which ones need attention or have something new, image vulnerabilities, and the deploys of the last 30 days per day with their success rate, followed by the running and stopped stacks
  • Stacks: a searchable, filterable list (running, stopped, with issues) next to the page of each stack: services, images with a new version, deployed and latest commit, and one-click Pull, Redeploy or Pull + Redeploy, also on several stacks at once
  • CI snippets: a ready-to-paste, syntax-highlighted curl per stack and action
  • History: every action launched from the dashboard or from CI, with who ran it and how it ended
  • Security: every image used by a stack scanned with Trivy: vulnerabilities by severity, which ones have a fix, and which stacks use each image. Images are scanned when they first show up, after every deploy (including the ones Komodo makes on its own, such as auto updates), every 24 hours to catch new CVEs, and on demand; private registries are tried and reported as "no access" when the scan cannot pull them
  • Failure alerts: optional ntfy notifications (ntfy.sh or self-hosted, with an optional access token) whenever a deploy fails
  • API keys for pipelines, a single Komodo connection, English and Spanish UI, dark and light themes

Monorepo (Turborepo + Bun workspaces), plus the production deployment using images published on GHCR.

Path Stack
Backend apps/backend Bun + Hono (thin server: auth, oRPC, OpenAPI)
Frontend apps/frontend Astro 7 (SSR) + React + TanStack Query
Gateway apps/gateway Caddy: the only published port, routes to both
Site apps/site Astro 7 (static), published to GitHub Pages
Trivy trivy service in compose.yml Upstream aquasec/trivy server for the image scans (no workspace)
Package Description
@komodo-cd/api oRPC routers (v0), Komodo, ntfy and Trivy services
@komodo-cd/auth Better Auth config + session resolver (cookie or x-api-key)
@komodo-cd/db Drizzle v1 (SQLite/libsql): schema, relations, migrations, seed
@komodo-cd/env Validated server env (t3-env + zod)
@komodo-cd/logger Shared pino logger
@komodo-cd/config Shared tsconfig

Overview

Stacks Deploy
Stacks Deploy
History Security
History Security
Connection API keys
Connection API keys

Stacks, light theme

The screenshots use made-up stacks and URLs.

Requirements

  • Docker + Docker Compose v2
  • Internet access to pull images

Quick start

One-line install (interactive)

Run it in the folder where the stack should live. It asks for the host port, public URL and initial admin, generates .env with a random BETTER_AUTH_SECRET (mode 600), downloads compose.yml and optionally starts the stack:

mkdir komodo-cd && cd komodo-cd
curl -fsSL https://raw.githubusercontent.com/Nonetss/Komodo-CD/main/scripts/bootstrap.sh | bash

Or from a clone:

git clone https://github.com/Nonetss/Komodo-CD.git
cd Komodo-CD
./scripts/bootstrap.sh

Pin a release with KCD_REF=v1.0.0 (defaults to main). The script never overwrites an existing .env.

Images are published as latest (the main branch) and with the release version (1.0.0, 1.0). To pin one, change the tags in compose.yml, for example ghcr.io/nonetss/komodo-cd-backend:1.0.0.


Manual setup

1. Generate secrets

# Better Auth secret (required)
openssl rand -base64 32

# Initial admin password (required)
openssl rand -base64 16

2. Configure environment

cp .env.example .env

Edit .env and fill at least these three variables:

APP_URL=https://your-domain.com
BETTER_AUTH_SECRET=<output of first openssl command>
SEED_ADMIN_PASSWORD=<output of second openssl command>

3. Start

docker compose up -d

The app will be available on the port configured in PORT (default 80).


compose.yml

services:
  backend:
    image: ghcr.io/nonetss/komodo-cd-backend:latest
    init: true
    restart: unless-stopped
    volumes:
      - db_data:/data
    environment:
      DATABASE_URL: "file:/data/db.sqlite"
      BETTER_AUTH_URL: "${APP_URL:?APP_URL is required}"
      BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET is required}"
      SEED_ADMIN_EMAIL: "${SEED_ADMIN_EMAIL:-admin@example.com}"
      SEED_ADMIN_NAME: "${SEED_ADMIN_NAME:-Admin}"
      SEED_ADMIN_PASSWORD: "${SEED_ADMIN_PASSWORD:?SEED_ADMIN_PASSWORD is required}"
      TRIVY_SERVER_URL: "${TRIVY_SERVER_URL:-http://trivy:4954}"
      TRIVY_RESCAN_INTERVAL_HOURS: "${TRIVY_RESCAN_INTERVAL_HOURS:-24}"
    healthcheck:
      test: ["CMD", "bun", "-e", "fetch('http://localhost:3000/health-check').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s
    networks:
      - komodo_net

  frontend:
    image: ghcr.io/nonetss/komodo-cd-frontend:latest
    init: true
    restart: unless-stopped
    environment:
      BACKEND_URL: "http://backend:3000"
    healthcheck:
      test: ["CMD", "bun", "-e", "fetch('http://localhost:4321/login').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - komodo_net

  gateway:
    image: ghcr.io/nonetss/komodo-cd-gateway:latest
    init: true
    restart: unless-stopped
    ports:
      - "${PORT:-80}:80"
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 5s
    depends_on:
      backend:
        condition: service_healthy
      frontend:
        condition: service_healthy
    networks:
      - komodo_net

  # Trivy server for the Security page: keeps the vulnerability database and
  # receives the scans the backend sends with its `trivy` client (same
  # version as TRIVY_VERSION in apps/backend/Dockerfile).
  trivy:
    image: aquasec/trivy:0.75.0
    init: true
    restart: unless-stopped
    command: ["server", "--listen", "0.0.0.0:4954", "--cache-dir", "/var/lib/trivy"]
    volumes:
      - trivy_cache:/var/lib/trivy
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:4954/healthz"]
      interval: 10s
      timeout: 5s
      retries: 5
      # The first start downloads the database before listening
      start_period: 120s
    networks:
      - komodo_net

networks:
  komodo_net:

volumes:
  db_data:
  trivy_cache:

Environment variables

Variable Required Description
APP_URL ✅ Public app URL, without trailing slash
BETTER_AUTH_SECRET ✅ Secret used to sign sessions. openssl rand -base64 32
SEED_ADMIN_PASSWORD ✅ Password for admin user created on startup
PORT — Host exposed port. Default 80
SEED_ADMIN_EMAIL — Admin email. Default admin@example.com
SEED_ADMIN_NAME — Admin name. Default Admin
TRIVY_SERVER_URL — Trivy server for image scanning. Default the trivy service (http://trivy:4954)
TRIVY_RESCAN_INTERVAL_HOURS — Hours before an image is scanned again on its own, to catch new CVEs. Default 24; 0 turns off this periodic rescan only

The trivy service downloads its vulnerability database from the Internet on first start (and keeps it updated), and the backend pulls the images to scan straight from their registries, so both need outbound access. Until the database is ready, scans fail and can be retried from the Security page.

GitHub Actions integration

Full example: image build and push + automatic deploy to a Komodo CD stack.

name: Build, Publish and Deploy

on:
  push:
    branches:
      - main
      - "v*"

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v7

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=ref,event=branch
            type=sha,prefix={{branch}}-,format=short

      - name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      - name: Pull + Redeploy stack
        run: |
          curl -X POST ${{ secrets.KOMODO_CD_URL }}/api/v0/deploy \
            -H "x-api-key: ${{ secrets.KOMODO_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"stack":"${{ vars.STACK_NAME }}","action":"pull-redeploy"}'

Required repository secrets and variables:

Key Type Description
KOMODO_CD_URL Secret URL of your Komodo CD instance (example: https://komodo-cd.example.com)
KOMODO_API_KEY Secret API key generated from the dashboard
STACK_NAME Variable Stack name in Komodo

Vulnerability results from CI

The same API key reads the Trivy results. GET /api/v0/security/images returns every image with its stacks and its counts per severity (images never scanned are queued by that same call), GET /api/v0/security/image?image=<ref> returns the vulnerabilities of one image, and POST /api/v0/security/scan queues a rescan ({"images": [...]}, or every image with an empty body). Every successful deploy already queues a scan of that stack's images, in the background; so does a deploy made inside Komodo (auto update, Komodo's UI, a procedure), picked up within about five minutes. To fail a job while the stack's images have critical vulnerabilities, wait until none is queued or scanning, then check:

for i in $(seq 1 60); do
  images=$(curl -sf "$KOMODO_CD_URL/api/v0/security/images" -H "x-api-key: $KOMODO_API_KEY" \
    | jq --arg s "$STACK_NAME" '[.images[] | select(.stacks | index($s))]')
  echo "$images" | jq -e 'all(.status != "queued" and .status != "scanning")' > /dev/null && break
  sleep 10
done
echo "$images" | jq -e 'map(.counts.critical) | add == 0'

Development

Requirements: Bun ≥ 1.4.

bun install

bun run setup:dev   # writes .env with a random secret and admin password

bun run dev            # Docker dev stack on http://localhost:4321 (hot reload, Ctrl+C stops)
bun run dev:down       # remove its containers (-v also drops its database)
bun run dev:local      # the same without Docker: backend (:3000) + frontend (:4321) with turbo watch
bun run dev:backend    # only backend, without Docker
bun run dev:frontend   # only frontend, without Docker
bun run dev:site       # only the project website (:4322)
Script Description
bun run build Build all apps (turbo build)
bun run check-types Type-check all apps
bun run format Format with Biome
bun run check Biome lint + format with autofix
bun run db:generate Generate Drizzle migrations (packages/db)
bun run db:studio Open Drizzle Studio (packages/db)
bun run docker:up Build images from source and start the stack

The Dockerfiles live in apps/*/Dockerfile but the build context is always the repo root:

docker build -f apps/backend/Dockerfile -t komodo-cd-backend .
docker build -f apps/frontend/Dockerfile -t komodo-cd-frontend .
docker build -f apps/gateway/Dockerfile -t komodo-cd-gateway .

.github/workflows/docker-build.yml builds and pushes the three images to ghcr.io/nonetss/komodo-cd-backend, ghcr.io/nonetss/komodo-cd-frontend and ghcr.io/nonetss/komodo-cd-gateway, rebuilding only the ones whose files changed:

Trigger Tags
Push to main latest, main, main-<sha>
Tag vX.Y.Z X.Y.Z, X.Y

The trivy service is not built here: it runs the upstream aquasec/trivy image, at the same version as TRIVY_VERSION in apps/backend/Dockerfile.

To publish a release, tag the commit and create the GitHub release:

git tag -a v1.0.0 -m "v1.0.0" && git push origin v1.0.0
gh release create v1.0.0 --generate-notes

Update to latest version

docker compose pull
docker compose up -d

Useful commands

# Show logs in real time
docker compose logs -f

# Show backend logs only
docker compose logs -f backend

# Restart one service
docker compose restart backend

# Stop everything
docker compose down

# Stop and remove DB volume (⚠️ deletes all data)
docker compose down -v

Persistent data

SQLite DB is stored in Docker volume db_data, mounted at /data inside the backend container. Data survives restarts and image updates. The Trivy vulnerability database lives in the trivy_cache volume; it can be deleted at any time and is downloaded again.

To make a manual backup:

docker run --rm \
  -v komodo_db_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/db-backup.tar.gz -C /data .

To restore:

docker run --rm \
  -v komodo_db_data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/db-backup.tar.gz -C /data

About

Komodo CD — Self-hosted dashboard to manage and trigger deployments on Komodo stacks. Supports pull, redeploy and pull+redeploy actions via web UI or API key, making it easy to integrate with GitHub Actions or any CI/CD pipeline.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages