惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
LINUX DO - 热门话题
S
Secure Thoughts
TaoSecurity Blog
TaoSecurity Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threat Research - Cisco Blogs
AI
AI
B
Blog RSS Feed
S
Schneier on Security
雷峰网
雷峰网
Schneier on Security
Schneier on Security
Help Net Security
Help Net Security
Cloudbric
Cloudbric
L
LINUX DO - 最新话题
罗磊的独立博客
有赞技术团队
有赞技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
The Cloudflare Blog
Webroot Blog
Webroot Blog
Last Week in AI
Last Week in AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
美团技术团队
L
Lohrmann on Cybersecurity
T
The Blog of Author Tim Ferriss
The Last Watchdog
The Last Watchdog
T
Troy Hunt's Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
Know Your Adversary
Know Your Adversary
O
OpenAI News
博客园 - 【当耐特】
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cybersecurity and Infrastructure Security Agency CISA
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
PCI Perspectives
PCI Perspectives
H
Heimdal Security Blog
I
InfoQ
GbyAI
GbyAI
T
Threatpost
C
Cisco Blogs

Show HN

GitHub - steveking-gh/firmion: Firmion is DSL and engine for firmware image generation. GitHub - villagesql/villagesql-skills: Agent skills for VillageSQL - gemini-cli-extension; claude-code-plugin GitHub - flightdeckhq/flightdeck: Observability and control plane for AI agents. CSP Radar GitHub - Light-Heart-Labs/DreamServer: Turn your PC, Mac, or Linux box into an AI server. LLM inference, chat UI, voice, agents, workflows, RAG, and image generation. GitHub - Diplomat-ai/diplomat-agent-ts: What can your TypeScript AI agent do to the real world? Scan your code. See which tool calls have zero checks Code Block Selector - Visual Studio Marketplace Prometheus dependency graph — interactive showcase | Riftmap Show HN: I made a vi-like modal keyboard plugin for Figma GitHub - run-llama/liteparse: A fast, helpful, and open-source document parser GitHub - dalemyers/Roar: A macOS CLI tool for notifications GitHub - district-solutions/open-agent-tools-coder: Enables small-to-large self-hosted ai models to use local source code when running tool-calling agentic workloads. We actively data mine 20,900+ (2+ TB) popular github repos using large and small ai models to create reuseable: json, markdown and parquet files for local-first tool-calling models. GitHub - progapandist/stripeek: A local TUI proxy for real-time Stripe API debugging, built for navigating complex payloads fast. GitHub - sir1st/hermes-desktop: All-in-one cross-platform desktop app for Hermes Agent — bundles Python + hermes-agent + hermes-web-ui GitHub - astefanutti/shaderbang: Shebang for Shaders Show HN: Generate Claude Code Workflows using Spec Driven Development approach GitHub - nixys/nxs-universal-chart: The Helm chart you can use to install any of your applications into Kubernetes/OpenShift Show HN: AI agents for UK GDAD PCF roles and their skills The Two Pillars: Mixer Mode and Meta-Software in the Reorganization of Software Work After AI GitHub - JaiCode08/teleport-env What 1,000+ Harness Experiments Taught Me About Self-Improving Agents Show HN: Liiists, a Markdown-first, iOS and CLI list app SwiperTab – Get this Extension for 🦊 Firefox (en-US) GitHub - kouhxp/fftext: Summarize, explain, fact-check, or translate any text, URL, or file. No GPU. No cloud. One command GitHub - sweetpad-dev/sweetpad: Develop Swift/iOS projects using VSCode GitHub - dogmaticdev/IRON: IRON a.k.a. Intermediate Representation Object Notation is a Interpreter/Database that is used to create Programming Languages. GitHub - sjhalani7/vaen: Package your AI coding harness into a portable .agent file, and share it across repos, teams, & the community without ever having to copy-paste instructions, skills, MCP config, or secrets. Show HN: Gandalf the Grader Show HN: Citadeld – replay any CI failure locally from a single file GitHub - tdortman/cuSBF: High-Performance GPU Super Bloom Filter coral-ai/claude-code-token-xray at main · Coral-Bricks-AI/coral-ai GitHub - ulyssestenn/funes: Funes is a Git-based framework for LLM-managed knowledge work: an AI Librarian ingests raw sources, builds an interlinked Markdown knowledge base, and uses it to produce cited reports, analyses, and other outputs. GitHub - ThatXliner/gah: Git Add Hunk, built for agents to use GitHub - harmont-dev/harmont-cli: Command-line client for the Harmont CI platform GitHub - brooksmcmillin/mcp-authflow: OAuth 2.0 Authorization Server framework for MCP servers GitHub - javaid-codes/audit-supply-chain-agents GitHub - amorey/gochan: A small library of common channel architectures for Go, inspired by Rust GitHub - arifozgun/OpenGem: Free, Open-Source AI API Gateway with Gemini, OpenAI & Anthropic Compatibility in 1 file GitHub - Pranesh950/BioPetals: 🌸 Run BIOxAI models at home, BitTorrent-style. Fine-tuning and inference up to 10x faster than offloading GitHub - cnguyen14/bounty-doctor: Diagnose a GitHub bounty issue before you waste hours: detects honeypot scam repos, AI-bot attempt swarms, and stale contests. Show HN: CoreMCP – MCP Server for On-Prem DBs Show HN: KittyHTML – Render HTML/CSS as an inline image in your terminal GitHub - bingud/filemat: Web-based file manager Show HN: TruthLens – Free multi-signal deepfake image detector GitHub - apexlocal-jz/claude-usage-tray: Windows system-tray app showing your Claude Code rate-limit usage at a glance. Zero deps, ~300 lines of PowerShell. Cross-IDE (works regardless of VS Code, Cursor, plain terminal). Release v0.1.2.1 · kouhxp/yapsnap GitHub - noopolis/moltnet: Self-hostable chat network for AI agents. Pre-built bridges for Claude Code, Codex, and the Claws. Rooms, DMs, history. No Slack bots, no Matrix, no glue code. GitHub - tamerh/enju: Coordinating Humans, AI Agents, and Compute as Peers on a Shared Workflow Graph Show HN: Continuity-auth – Respect-weighted rate limits for the open web GitHub - luml-ai/luml: AI lifecycle platform where engineers and agents track experiments, train models, and ship to production. GitHub - mrdanielcasper/CoreTex: A UNIX-inspired, biomimetic, flat-file AI harness and knowledge engine. GitHub - clemg/pierre-github: Pierre's diffs.com and trees.software for Github GitHub - lyriks-io/unspaghettit: Behavior-driven AI development without prompt spaghetti. GitHub - sofumel/claude-handoff-revive: Resume Claude Code work after rate/usage/context limits without replaying the prior transcript. Auto-saves at 90%/95% usage. Plugin-installable, 10 languages. GitHub - dotexorg/saferpc: Typed, end-to-end encrypted RPC over any bidirectional channel. GitHub - BeeZeeAgent/beezee: Agent harness orchestration Legato Next.js Boilerplate for Internal Tools · CoreUI GitHub - clark-labs-inc/clark-hash: Clark Hash, 32x smaller searchable sketches for embeddings GitHub - ZeroPointRepo/youtube-mcp: The fastest YouTube transcript + YouTube search MCP for AI agents. Try for free. Typing Mastery — climb toward 100+ WPM, deliberately GitHub - Andebugulin/Awareen GitHub - fayzan123/claude-workflow-composer: Visual desktop app for composing multi-agent coding workflows. Drag agents, attach skills and MCPs, wire handoffs, export to .claude/ GitHub - harshaneel/humanize: Best static AI text humanizer. Two research-grounded skills that work in any LLM (Claude, ChatGPT, Gemini, Codex): humanize beats perplexity-based detectors, ai-check produces forensic scoring with evidence-quoted flags. Nine levers, 50+ peer-reviewed sources, 2024-2026 detection literature. GitHub - StackOneHQ/stack-nudge GitHub - nodes-app/swift-markdown-engine: A native AppKit Markdown editor for macOS, built on TextKit 2 and bridged to SwiftUI. We hardened an LLM agent. Each defense we added made it more exploitable. GitHub - alkait/WhatsKept: Agent-queryable WhatsApp history from an iOS backup — a single Go binary. GitHub - octelium/cordium: Open-source, general-purpose sandbox platform for devs and AI agents that provides identity-based secure access to infrastructure without credentials. WAR.GOV/UFO Microfilm5 GitHub - scosman/videowright: Build animated explainer videos with your coding agent GitHub - dipankar/dscode: The code editor you can take apart. GitHub - zoharbabin/web-researcher-mcp: MCP server (Go) for AI assistants: web search, content extraction, academic/patent/news research. Multi-provider routing, 4-tier scraping, search lenses. Works with Claude, Cursor, and any MCP client. GitHub - ruvnet/RuView: π RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video. GitHub - scanaislop/aislop: Catch the slop AI coding agents leave in your code: narrative comments, swallowed exceptions, as-any casts, dead code, oversized functions. 50+ rules across 7 languages (TypeScript, JavaScript, Python, Go, Rust, Ruby, PHP). Sub-second, deterministic, no LLM at runtime. MIT-licensed. GitHub - kouhxp/cheap-im: CPU-only voice agent approximating Thinking Machines' Interaction Models demo GitHub - unprovable/OrchidMantis: Orchid Mantis — standalone framework for Zero-Knowledge Proofs of eXploit (ZKPoX). GitHub - MarcellM01/TinySearch: Shrink the web for your local LLMs! GitHub - TangibleResearch/Halgorithem: A Algo designed to detect AI Hallucitions GitHub - DO-SAY-GO/freelang: I love freelang GitHub - CarpseDeam/Aura-IDE: An AI coding harness that shaped itself - Planner/Worker agents, repo awareness, surgical edits, validation, recovery, and safe diff approvals. GitHub - chojs23/concord: A feature-rich TUI client for Discord GitHub - tommyjepsen/awesome-ux-skills: UX & AI Product designs skills you can use today in Claude Code GitHub - aerf-spec/aerf: Agent Evidence Receipt Format (AERF) — an open specification for tamper-evident, independently verifiable records of AI agent actions. GitHub - kklimuk/docx-cli: CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity. GitHub - Jwrede/tokentoll: Catch LLM cost changes in code review. Infracost for LLM spend. GitHub - samchon/ttsc: A `typescript-go` toolchain for compiler-powered plugins and type-safe execution + 500x faster lint integrated into compiler GitHub - Higangssh/homebutler: 🏠 Manage your homelab from chat. Single binary, zero dependencies. GitHub - olalie/tapmap: See where your computer connects and what stands out on a live world map. GitHub - Diplomat-ai/diplomat-agent: What can your AI agent do to the real world? Scan your code. See which tool calls have zero checks GitHub - Bajusz15/beacon: Open-source agent for secure remote access, monitoring, and deploys across home-lab and self-hosted machines like Raspberry Pi, N100, or any Linux server. Open web based TTY or tunnel Home Assistant and other local services securely without opening ports. BigTech AI News - Chrome 应用商店 GitHub - vinhnx/VTCode: VT Code is an open-source coding agent with LLM-native code understanding and robust shell safety. Supports multiple LLM providers with automatic failover and efficient context management. GitHub - michaelaz774/decision-engine: A decision operating system for startup founders, powered by Claude Code. Synthesizes wisdom from 25+ legendary founders and investors into interactive AI-driven decision frameworks. GitHub - Chrilleweb/dotenv-diff: Validate environment variable usage in your codebase GitHub - Lumen-Labs/brainapi2: BrainAPI is a knowledge graph–powered AI memory layer that transforms unstructured data into structured knowledge, enabling intelligent search, recommendations, and contextual memory for AI agents and applications. GitHub - familiar-software/familiar: Let AI watch you work. Familiar lets your AI update its memory, skills, and knowledge by watching your screen. GitHub - skorotkiewicz/rudo: A small, elegant dock for Wayland GitHub - muxshed/shed: One stream in, or many. Every destination, simultaneously. No cloud middleman, no per-channel fees, no limits. make sidebar/address bar rounded corner toggleable
GitHub - kaupang-dev/kaupang
witnz · 2026-06-17 · via Show HN

kaupang (KOW-pang) — Old Norse for a Viking-age trading hub, from kaup "trade" + vangr "field, place." A kaupang was where goods from every craft were gathered, packed, and loaded onto ships bound for distant shores. This tool is that hub for your software: it gathers your services, pins them into a sealed, portable cargo, and dispatches them anywhere — your laptop, a CI agent, a remote swarm, or an airgapped site across the water. 🛶

Spin up environments on Docker Compose, Docker Swarm, or Kubernetes from a single TypeScript config — the same command on your laptop and in CI.

kaupang is an imperative, push-based deploy tool — an environment maker, not a reconciler. You run it, it makes a target match your config, it records exactly what it did, and you can replay or roll back. No control loop, no cluster agent.

kaupang up api --target staging      # build the plan, resolve images, deploy
kaupang up api --dry-run             # show the dependency graph + commands, run nothing
kaupang rollback api --target prod   # re-apply the previous good deployment
kaupang down api                     # tear it back down

Why a DevOps engineer might care

  • One tool, laptop to pipeline. kaupang up api --target staging is byte-for-byte the same locally and on an agent. Only the target (context, env, backend) differs.
  • Config is code. Environments are TypeScript, so image tags and env values can come straight from process.env / pipeline variables — no templating language.
  • Immutable, auditable deploys. Every deploy resolves image tags to a digest, pins it, and records the whole artifact in a ledger. rollback replays a known-good snapshot.
  • Promotion is trivial. Staging validates a digest; prod redeploys that exact digest. "What I tested is what I ship."
  • Multi-backend. Swarm staging and Kubernetes prod from the same environment definition.

Contents

  • Quick start
  • Config
  • Environments & services
  • Catalog (shared presets)
  • Targets (staging vs prod)
  • Solutions & bundles
  • Pipelines
  • Single-service repo
  • Versioning: resolve, pin, record, roll back
  • Secrets
  • Pull behavior
  • Build & hooks
  • Dependency resolution
  • Backends
  • Using kaupang in Azure DevOps
  • Command reference
  • Develop
  • Roadmap

Quick start

npm install -g @kaupang/cli        # or use via npx in CI

Create a kaupang.config.ts at your repo root and an environments/ folder:

// kaupang.config.ts
import { defineConfig } from "@kaupang/core";

export default defineConfig({
  environments: "./environments",
  project: "longhall",
  defaultBackend: "compose",
});
// environments/api.ts
import { defineEnvironment } from "@kaupang/core";

export default defineEnvironment({
  services: {
    web: { image: "ghcr.io/midgard/longhall-api:latest", ports: ["8080:3000"] },
  },
});
kaupang up api            # deploys locally with docker compose
kaupang up api --dry-run  # preview the plan first

Config

import { defineConfig } from "@kaupang/core";

export default defineConfig({
  environments: "./environments",     // folder of environment files
  project: "longhall",                    // namespaces stacks / projects / namespaces
  defaultBackend: "compose",          // compose | swarm | kubernetes
  dockerRepository: "ghcr.io/midgard",   // prefix applied to bare image names
  defaultPull: "missing",             // always | missing | never
  cacheDir: ".kaupang",               // where generated files + the ledger live
  globalEnv: { TZ: "UTC" },           // injected into every service everywhere
  catalog: { sources: [{ type: "file", path: "./catalog.json" }] },
  targets: { /* see Targets */ },
});

Env var precedence (low → high): globalEnv < environment env < target env < service env.

Not a TypeScript repo? You don't need one. kaupang loads its config from kaupang.config.ts, .mjs, .js, or .json (and environments/* in any of those) — no tsconfig, no typescript dependency, no build step. Pick what fits:

  • .ts — full type-checking + autocomplete (the extension is transpiled at runtime).
  • .js / .mjs — plain JavaScript objects, with comments and the secret() / use() helpers. Add /** @type {import('kaupang').KaupangConfig} */ for checking.
  • .json — pure data, ideal for machine-generated configs. The helpers become literal tags: secret("X"){ "$secret": "X" }, use("postgres"){ "$catalog": "postgres" }. (No comments, and you write the tags by hand.)

The define* helpers are identity functions, so a plain object — or a JSON file — is all they ever produce.


Environments & services

An environment is a docker-compose-like definition, but in TypeScript. The ergonomic surface is deliberately small:

import { defineEnvironment, use } from "@kaupang/core";

export default defineEnvironment({
  dependsOn: "postgres",                  // string or string[] — other environments
  services: {
    db: use("postgres"),                  // a whole service from the catalog
    web: "longhall-api:latest",               // string shorthand → { image: "longhall-api:latest" }
    worker: { image: "longhall-api", dependsOn: "web" },
  },
});
  • A service can be a plain string (the image), a full object, or a catalog reference via use().
  • dependsOn accepts a string or an array, at both the environment and service level.
  • With dockerRepository set, bare names (longhall-api) get prefixed (ghcr.io/midgard/longhall-api). Fully-qualified names (anything with a /) and catalog presets are left alone, so public images stay public.

Because the file is TypeScript, dynamic values just work:

web: { image: `longhall-api:${process.env.LONGHALL_API_TAG ?? "latest"}` }

That single line is what lets a pipeline pin a build by setting one env var.


Catalog (shared service presets)

Reusable service definitions that live outside your repo and are pulled in at load time — so a brand-new environment is a few use() calls plus your own service. A catalog is a manifest of named presets:

{
  "services": {
    "postgres": { "image": "postgres:16-alpine", "ports": ["5432:5432"],
                  "healthcheck": { "test": ["CMD-SHELL", "pg_isready"] } },
    "redis":    { "image": "redis:7-alpine", "ports": ["6379:6379"] }
  }
}

Reference presets with use("name", overrides?). Sources are pluggable (src/catalog/source.ts implements CatalogSource); earlier sources win:

source status use it for
file shipped a manifest committed to a shared infra repo / vendored
http shipped a static manifest served by an internal endpoint
service shipped a running internal catalog service kaupang queries
oci shipped a versioned catalog published as an OCI artifact (ORAS)

Catalog service contract

The service source (a long-running internal catalog — e.g. a container your platform team owns) just needs to answer a GET with JSON in this shape; both keys are optional, so one service can serve presets and solutions together:

catalog: { sources: [{ type: "service", url: "https://catalog.internal", headers: { Authorization: "Bearer …" } }] }

That single service is the "container that handles all things" — your shared service presets and the solution recipes teams compose on the fly.

OCI catalog (setup guide)

If you'd rather version the catalog as an immutable artifact in a registry you already run (ACR, Harbor, ghcr, ECR …), publish it with ORAS and reference it by ref. No new infrastructure — it reuses your image registry.

# 1. Author the manifest (services and/or solutions).
cat > catalog.json <<'JSON'
{ "services":  { "postgres": { "image": "postgres:16-alpine", "ports": ["5432:5432"] } },
  "solutions": { "hedeby": { "version": "2.3.1", "environments": ["api"] } } }
JSON

# 2. Authenticate to the registry (or `docker login` — oras reuses it).
oras login midgardregistry.azurecr.io -u <user> -p <token>

# 3. Push it as a versioned OCI artifact.
oras push midgardregistry.azurecr.io/kaupang-catalog:1 catalog.json
catalog: {
  sources: [{ type: "oci", ref: "midgardregistry.azurecr.io/kaupang-catalog:1" }],
}

kaupang pulls it with oras pull at load time, using your existing oras/docker login credentials — the same auth story as image resolution, so there's nothing extra to set up. By default it reads catalog.json from the artifact; push a different filename and set it via the source's path. Pin a tag (or a digest) for reproducibility, exactly like images.


Targets (staging vs prod)

A target is where kaupang deploys: a remote cluster, a remote host, or your machine. The same environment definition deploys to every target — only the target's backend, env, and context differ. No duplicated environment files to drift.

targets: {
  local: {},                                   // your default docker context
  staging: {
    backend: "swarm",
    dockerContext: "staging-swarm",            // → docker --context staging-swarm …
    pull: "always",                            // floating channel, roll forward
    env: { APP_ENV: "staging", PUBLIC_URL: "https://staging.longhall.example" },
  },
  prod: {
    backend: "kubernetes",
    kubeContext: "aks-prod",                   // → kubectl --context aks-prod …
    kubeconfig: "./kubeconfig-prod",           // sets KUBECONFIG for the run
    pull: "missing",                           // deploy the digest staging validated
    env: { APP_ENV: "production", PUBLIC_URL: "https://longhall.example" },
  },
},
kaupang up api --target staging   # docker --context staging-swarm stack deploy …
kaupang up api --target prod      # kubectl --context aks-prod apply …

Context selection rides on the tools' own mechanisms — docker --context / DOCKER_HOST and kubectl --context / KUBECONFIG. Authenticate in your pipeline as usual (az acr login, az aks get-credentials) and kaupang inherits it. An unknown --target is a hard error that lists the configured ones. The ledger is scoped per environment@target, so staging and prod keep separate histories and roll back independently.


Solutions & bundles

A solution is a named, versioned composition of environments — a whole customized product for a customer or site. Recipes can live inline in config or, more usefully, in the hosted catalog (so they're composed on the fly without a repo):

solutions: {
  "hedeby": {
    version: "2.3.1",
    environments: ["birka", "ribe"], // dependencies pulled in automatically
    pins: { "birka.web": "longhall-api@sha256:…" }, // per-service version pins
    env: { JARL: "x" },
    target: "site-x",
  },
}

Deploy a solution directly (online — resolves images and deploys):

kaupang up --solution hedeby --target site-x

Or bundle it into a portable, fully-pinned artifact — the unit a pipeline produces, and what crosses an air gap:

kaupang bundle hedeby --target site-x --with-images -o ./hedeby.bundle

A bundle is self-contained and relocatable: it holds the generated artifacts and commands with bundle-relative paths, plus (with --with-images) docker saved image tarballs. Deploy it with no source repo and no registry:

# inside an airgapped network, after extracting the 7z:
kaupang up --bundle ./hedeby.bundle --target site-x

Or distribute the bundle through a registry — the bridge between the online and airgap flows. Push a materialized bundle as a single versioned OCI artifact, then pull-and-deploy it anywhere by ref:

kaupang bundle hedeby --target site-x --push oci://midgardregistry.azurecr.io/bundles/hedeby:2.3.1
kaupang up --bundle oci://midgardregistry.azurecr.io/bundles/hedeby:2.3.1 --target site-x

Push/pull use oras (same auth as the OCI catalog source), so a solution becomes a versioned, pullable thing in the registry you already run. up --bundle accepts either a local directory or an oci:// ref.

up --bundle loads any included images and replays the pinned artifacts through the target's context — the same deploy path as everything else, just offline. It records to the ledger like any deploy, so rollback works inside the gap too (bounded by which versions were carried in).

The chain: service presets → solution recipe (catalog) → bundle (pipeline, pinned

  • images) → target → live URL. Online and airgapped use the same bundle artifact; only the transport differs.

Pipelines

A pipeline is an ordered set of steps — shell commands, deploys, builds, and waits — wired together as a DAG. It's the recipe part of CI: the portable sequence of kaupang actions that runs byte-for-byte the same on your laptop and on an agent. The CI system still owns triggers, approvals, and secrets; the pipeline owns what actually happens.

import { defineConfig, definePipeline } from "@kaupang/core";

export default defineConfig({
  // …environments, targets…
  pipelines: {
    release: definePipeline({
      steps: {
        build:  { run: "npm run build" },
        deploy: { up: "api", needs: "build" },
        smoke:  { wait: { http: "http://localhost:8080/health", timeout: "30s" }, needs: "deploy" },
        done:   { run: "echo released", needs: "smoke" },
      },
    }),
  },
});
kaupang run release --target staging
kaupang run release --dry-run     # print the step DAG, run nothing

Each step declares exactly one action and optional needs:

action does
run: "<cmd>" runs a shell command from the config root
up: "<env>" deploys an environment — same path as kaupang up <env>
down: "<env>" tears an environment down
build: "<env>" builds the environment's images
wait: { … } gates on http (poll a URL for a status) or seconds (sleep)

Steps are topologically sorted from needs; cycles are rejected up front, and the dry-run shows which steps fall in the same wave (independent, so safe to parallelize later). up / down / build steps honor --target (or a per-step target / backend override) and go through the identical resolve → pin → materialize → ledger path as the top-level commands — a pipeline up is a real, recorded deploy, not a second code path. wait example:

smoke: { wait: { http: "https://api.longhall.example/health", status: 200, interval: "2s", timeout: "2m" }, needs: "deploy" }

In Azure DevOps this is one step inside a stage — npx @kaupang/cli run release --target staging — so approvals/gates stay in the YAML while the deploy recipe stays in kaupang and stays runnable locally.


Versioning: resolve, pin, record, roll back

Every up resolves each service's image reference to an immutable digest at deploy time, pins that digest into the generated artifact, and records the whole deployment in a ledger (.kaupang/ledger.json). One mechanism supports both intents — you choose by what you write:

  • Pinnedimage: "longhall-api@sha256:…" or use("longhall-api@1.4.2"). A named version; re-running up won't roll forward unless you edit the file.
  • Floatingimage: "longhall-api:latest", a channel like @stable, or use("longhall-api"). May point somewhere new each deploy, so up resolves and records each time and can roll the environment forward.

Either way, the thing that lands on the target is a digest, so a persistent remote deploy never silently drifts when someone re-pushes a tag. Resolution uses docker buildx imagetools inspect with the agent's existing login. Two escape hatches: a floating ref that resolves to nothing is a hard error (never deploy stale), and --no-resolve deploys references as-is — needed in the local loop where you kaupang build an image that was never pushed.

The ledger stores the exact artifact and commands that were applied, so rollback is just "replay a known-good snapshot":

kaupang rollback api --list                       # show history with digests
kaupang rollback api                              # re-apply the previous good deploy
kaupang rollback api --to 20260611T140000Z-bbbb   # roll to a specific deployment
kaupang rollback api --dry-run                    # print the replay, run nothing

Because the target is declarative, rollback runs the same path as up — no reconciler, no diffing. Each rollback is itself recorded, so you can roll back a rollback.

Handing resolved digests to a later stage

up --output json prints what was actually deployed — including the digest each floating ref resolved to — as clean JSON on stdout (progress logs are suppressed). That's the explicit "resolve once in staging, promote the same digest to prod" handoff:

# staging stage: deploy and capture exactly what landed
kaupang up api --target staging --output json > resolved.json
{
  "project": "longhall",
  "target": "staging",
  "backend": "swarm",
  "deployments": [
    { "environment": "api", "deploymentId": "20260612T…-ab12", "ranAt": "",
      "images": [ { "service": "web", "ref": "ghcr.io/midgard/longhall-api:latest",
                    "digest": "sha256:9f86d0…", "pinned": true } ] }
  ]
}

A later prod stage reads resolved.json and deploys those digests, with no second resolution and no chance of a tag moving underneath you. --dry-run --output json emits the planned graph (no digests) for inspection or gating instead.


Secrets

Wrap any env value in secret("VAR") and kaupang emits a reference instead of the value — so secrets never land in the generated artifact or the ledger:

import { defineEnvironment, secret } from "@kaupang/core";

export default defineEnvironment({
  services: {
    web: {
      image: "longhall-api",
      env: {
        LOG_LEVEL: "info",                  // literal — persisted (fine, aids rollback)
        API_KEY: secret("LONGHALL_JARL_KEY"),    // → resolved at runtime, never written
        DB_PASS: secret("PROD_DB_PASSWORD"),// container var DB_PASS ← host PROD_DB_PASSWORD
      },
    },
  },
});
  • Compose / Swarm: emitted as ${LONGHALL_JARL_KEY} and interpolated by Docker at runtime from the process env. kaupang validates up front that every referenced var is present and fails fast with a clear message if not — so you never deploy with a silently-empty secret.
  • Kubernetes: emitted as a secretKeyRef into a Secret named <project>-secrets (key = the source var), which you manage in the cluster (e.g. kubectl create secret, External Secrets, or a CSI driver). The value never enters the manifest.

Non-secret config (a literal string) is still persisted in the artifact and ledger on purpose — that's what makes rollback hermetic. Only marked values are held out.


Pull behavior

  • Per service: pull: "always" | "missing" | "never" → Compose pull_policy.
  • Whole run: --pull always → Compose up --pull always; Swarm stack deploy --resolve-image=always (re-resolves tags to the newest digest).
  • Honest caveat: there is no portable "pull latest only if it exists" — docker pull errors on a missing tag. --pull missing is the safe default; registries without a latest tag are fine as long as you pin tags.

Build & hooks

defineEnvironment({
  hooks: {
    beforeUp: ["npm run build"],            // shell string, or { run, cwd, env }
    afterUp: [{ run: "./smoke-test.sh", cwd: "./scripts" }],
  },
  services: { web: { build: "./web" } },    // docker compose build picks this up
});

kaupang build <env> runs docker compose build for services that declare a build context, tagging each as its (repository-prefixed) image. Add --push to docker compose push them to the registry in the same step. Hooks run around up/down (beforeUp / afterUp / beforeDown / afterDown).


Using kaupang in a single-service repo (Python, C++, anything)

kaupang operates on your image, not your source — so the service's language is irrelevant. Whatever turns your code into a container image (a Dockerfile, buildpacks, ko, …) is yours to keep; kaupang builds/pushes/deploys the result. A Python or C++ repo needs three small things: the Dockerfile you'd have anyway, plus two plain-JS files (no TypeScript).

my-service/
├─ Dockerfile               # your build — Python, C++, Go, whatever
├─ kaupang.config.mjs
└─ environments/
   └─ app.mjs
// kaupang.config.mjs
export default {
  environments: "./environments",
  project: "my-service",
  dockerRepository: "ghcr.io/me",   // bare image names get this prefix
  targets: {
    local: {},                                              // your machine
    prod: { backend: "swarm", dockerContext: "prod-swarm", pull: "missing" },
  },
};
// environments/app.mjs
export default {
  services: {
    app: {
      image: "my-service",          // publish/deploy target → ghcr.io/me/my-service
      build: { context: "." },      // build from ./Dockerfile
      ports: ["8000:8000"],
    },
  },
};

A Python Dockerfile is just the usual FROM python:3.12-slimpip installCMD ["uvicorn", "main:app", …]; a C++ one is a multi-stage compile (FROM gcc → build → copy the binary into a slim runtime). kaupang doesn't care which — it only needs the resulting image tag. Then:

kaupang build app                 # docker compose build  → tags ghcr.io/me/my-service
kaupang build app --push          # build, then push it to the registry
kaupang up app                    # run it locally via compose
kaupang up app --target prod      # resolve the pushed image to a digest, deploy to swarm

That's the whole loop in one repo: build, publish, deploy — same commands locally and in CI. The catalog/solutions/bundle machinery is there when you grow into multiple services or sites, but a single service uses none of it.


Dependency resolution

Two levels, both topologically sorted with cycle detection: between environments (dependsOn, resolved recursively) and between services within an environment. --dry-run renders both, grouping services into parallelizable waves:

Environment start order:
  1. ● postgres
  2. ● redis
  3. ◆ api  ← depends on postgres, redis

  api
  ├─ wave 1: migrate
  └─ wave 2: parallel web, worker

Backends

Backend up build Notes
compose docker compose up -d --wait docker compose build Full support.
swarm docker stack deploy — (build + push in CI) Uses the deploy key; cross-environment ordering handled by kaupang.
kubernetes kubectl apply — (build + push in CI) Namespace + Deployment (+ Service) per service. Needs prebuilt images.

Using kaupang in Azure DevOps

kaupang runs as an ordinary step on a Microsoft-hosted or self-hosted agent. The pattern that gets the most value out of it:

  1. Build & push the application image (immutable, build-id–tagged).
  2. Deploy to staging with kaupang — it resolves the tag to a digest, pins it, smoke-tests, and records the deployment.
  3. Promote to prod behind an approval — deploy the same tag, which resolves to the same digest staging validated.

Azure variables gotcha: non-secret pipeline / variable-group values are auto-exposed as environment variables, but secret variables are not — you must map them explicitly in the step's env: block (FOO: $(foo)). kaupang (a Node process) and the docker/kubectl it spawns read from that environment.

1. Multi-stage build → staging → prod (the main pattern)

# azure-pipelines.yml
trigger:
  branches: { include: [ main ] }

variables:
  imageTag: $(Build.BuildId)          # immutable tag — the key to safe promotion
  registry: midgardregistry.azurecr.io

pool:
  vmImage: ubuntu-latest

stages:
  - stage: Build
    jobs:
      - job: build
        steps:
          - checkout: self
          - script: az acr login --name midgardregistry
            displayName: Registry login
          - script: |
              docker build -t $(registry)/longhall-api:$(imageTag) .
              docker push $(registry)/longhall-api:$(imageTag)
            displayName: Build & push image

  - stage: Staging
    dependsOn: Build
    jobs:
      - deployment: deployStaging
        environment: staging          # auto-deploys (no approval attached)
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: NodeTool@0
                  inputs: { versionSpec: "20.x" }
                # Resolves longhall-api:$(imageTag) → a digest, pins it, records it.
                - script: npx @kaupang/cli up api --target staging
                  displayName: Deploy to staging
                  env:
                    LONGHALL_API_TAG: $(imageTag)
                    DOCKER_HOST: $(STAGING_DOCKER_HOST)   # secret → mapped here
                    LONGHALL_JARL_KEY: $(jarlKey)           # secret() ref → mapped here
                - script: ./scripts/smoke-test.sh https://staging.longhall.example
                  displayName: Smoke test

  - stage: Production
    dependsOn: Staging
    jobs:
      - deployment: deployProd
        environment: production       # attach a manual-approval check in the UI
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: NodeTool@0
                  inputs: { versionSpec: "20.x" }
                # Same immutable tag ⇒ same digest staging validated.
                - script: npx @kaupang/cli up api --target prod
                  displayName: Promote to production
                  env:
                    LONGHALL_API_TAG: $(imageTag)
                    KUBECONFIG: $(PROD_KUBECONFIG)        # secret file path
                    # API_KEY comes from the in-cluster <project>-secrets Secret on k8s

The matching environment file reads the tag from the pipeline:

// environments/api.ts
export default defineEnvironment({
  services: {
    web: { image: `longhall-api:${process.env.LONGHALL_API_TAG ?? "latest"}`, ports: ["8080:3000"] },
  },
});

The production Azure DevOps Environment is where you attach the approval/check, so the promote stage waits for a human before kaupang up api --target prod runs.

2. Validate config on every PR (cheap gate, no deploy)

# azure-pipelines-pr.yml
pr:
  branches: { include: [ main ] }

pool: { vmImage: ubuntu-latest }

steps:
  - checkout: self
  - task: NodeTool@0
    inputs: { versionSpec: "20.x" }
  # Fails the PR if the config doesn't resolve; prints the dependency graph.
  - script: npx @kaupang/cli up api --target staging --dry-run
    displayName: Validate deploy plan

--dry-run is hermetic (no registry calls, no deploy), so it's a fast, safe pre-merge check that the graph resolves and the commands look right.

3. One-click rollback pipeline

# azure-pipelines-rollback.yml  (manual trigger)
trigger: none

parameters:
  - name: target
    type: string
    default: prod
  - name: to
    type: string
    default: ""          # empty = previous good deployment

pool: { vmImage: ubuntu-latest }

steps:
  - checkout: self
  - task: NodeTool@0
    inputs: { versionSpec: "20.x" }
  - script: az aks get-credentials --name midgard-${{ parameters.target }} --resource-group midgard
    displayName: Cluster login
  - script: |
      if [ -n "${{ parameters.to }}" ]; then
        npx @kaupang/cli rollback api --target ${{ parameters.target }} --to ${{ parameters.to }}
      else
        npx @kaupang/cli rollback api --target ${{ parameters.target }}
      fi
    displayName: Roll back api

For rollback to find history, the ledger must persist between runs (an agent workspace is ephemeral). Commit it to a deploy repo, publish it as a pipeline artifact, or — recommended — point cacheDir at a mounted volume on a self-hosted agent so the ledger survives.

Separating the app and deploy repos

If kaupang config lives in a dedicated deploy repo, check both out:

resources:
  repositories:
    - repository: deploy
      type: git
      name: midgard/deploy

steps:
  - checkout: self        # the application repo
  - checkout: deploy      # the kaupang config + environments
  - script: npx @kaupang/cli up api --target staging --cwd $(Build.SourcesDirectory)/deploy
    env: { LONGHALL_API_TAG: $(imageTag) }

The app repo publishes immutable images; the deploy repo declares and rolls them out. kaupang's catalog is the natural seam between them: the app's publish pipeline registers longhall-api → …@digest, and the deploy repo's use("longhall-api") picks it up.


Command reference

kaupang up <env>          [-b ...] [--target <t>] [--pull ...] [--no-resolve] [--dry-run] [--output json] [--cwd <dir>]
kaupang up --solution <s> [--target <t>] [--pull ...] [--no-resolve] [--dry-run] [--output json] [--cwd <dir>]
kaupang up --bundle <dir|oci://ref> [--target <t>] [--dry-run] [--cwd <dir>]   # offline / airgap
kaupang down <env>        [-b ...] [--target <t>] [--with-deps] [--dry-run] [--cwd <dir>]
kaupang build <env>       [-b ...] [--push] [--dry-run] [--cwd <dir>]
kaupang bundle <solution> [--target <t>] [--with-images] [--push oci://ref] [-o <dir>] [--no-resolve] [--cwd <dir>]
kaupang rollback <env>    [--to <id>] [--target <t>] [--list] [--dry-run] [--cwd <dir>]
kaupang run <pipeline>    [--target <t>] [--dry-run] [--cwd <dir>]

--target defaults to local (or $KAUPANG_TARGET). --cwd resolves the config from a directory other than the current one.


Develop

npm install
npm run dev -- up web --dry-run --cwd examples/minimal   # run from source via jiti
npm run build                                            # bundle to dist/ with tsup
npm run typecheck
npm test                                                 # vitest suite (offline)

The examples/ folder is a gallery of runnable samples, each named for the one concept it isolates — minimal, build-from-dockerfile, runonce-migrations, catalog-presets, secrets, kubernetes, pipeline, and json-config. Run any with --cwd examples/<folder>.

The flagship is examples/longhall/ — a tiny Viking trading-post backend that exercises everything together: a saga store and a runes cache (both from a file catalog) plus a market that builds its image from a local Dockerfile and depends on them, deployable to the local, vanaheim, and asgard realms (targets). It also defines a longhall-full solution and a voyage pipeline.


Roadmap

The core is in place: multi-backend deploys, targets, the catalog (file/http/service/oci), solutions, OCI bundles, pipelines, the ledger + rollback, and --output json digest handoff. Ideas under consideration next:

  • Parallel execution within a pipeline wave — independent steps in the same wave currently run sequentially; they could run concurrently.
  • Surface the resolved public URL on a successful up for quick smoke-testing.
  • Per-site rollback bounds in airgaps — constrain rollback to the versions a bundle actually carried in.

kaupang stays an imperative push tool: it makes a target match your config, records what it did, and replays on request. It is deliberately not a continuous reconciler — if you need git-driven self-healing, reach for Argo CD or Flux. The niche here is great ergonomics and local/CI parity for teams that want to run a deploy, not operate a control plane.