<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andreas Quist Batista</title>
    <description>The latest articles on DEV Community by Andreas Quist Batista (@clownay).</description>
    <link>https://dev.to/clownay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3982896%2Fdf30fb94-cea2-4344-a20d-5887dce3f026.jpeg</url>
      <title>DEV Community: Andreas Quist Batista</title>
      <link>https://dev.to/clownay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clownay"/>
    <language>en</language>
    <item>
      <title>Kaupang: deploy the same config to Docker Compose, Swarm, or Kubernetes</title>
      <dc:creator>Andreas Quist Batista</dc:creator>
      <pubDate>Sat, 13 Jun 2026 16:04:11 +0000</pubDate>
      <link>https://dev.to/clownay/kaupang-deploy-the-same-config-to-docker-compose-swarm-or-kubernetes-1a08</link>
      <guid>https://dev.to/clownay/kaupang-deploy-the-same-config-to-docker-compose-swarm-or-kubernetes-1a08</guid>
      <description>&lt;p&gt;I just released &lt;strong&gt;kaupang&lt;/strong&gt; — Old Norse for a Viking trading hub, where goods from every craft were gathered, packed, and shipped. It's an imperative, push-based deploy&lt;br&gt;
CLI: you point it at a config, it makes a target match it, records exactly what it did, and lets you replay or roll back. The same command runs on your laptop and in CI.&lt;/p&gt;
&lt;h2&gt;
  
  
  The itch
&lt;/h2&gt;

&lt;p&gt;Every project ended up with its own deploy glue — a compose file here, a &lt;code&gt;kubectl apply&lt;/code&gt; there, a hand-rolled promotion script — and none of it behaved the same locally as it did in the pipeline. I wanted &lt;em&gt;one&lt;/em&gt; definition that could target whatever backend a given environment used, run identically everywhere, and make "what I tested is what I ship" the default instead of a hope.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// kaupang.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@kaupang/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;environments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./environments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dockerRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ghcr.io/acme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// environments/api.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineEnvironment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@kaupang/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEnvironment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shop-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8080:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kaupang up api &lt;span class="nt"&gt;--target&lt;/span&gt; staging   &lt;span class="c"&gt;# resolve images → pin digests → deploy → record&lt;/span&gt;
kaupang up api &lt;span class="nt"&gt;--dry-run&lt;/span&gt;          &lt;span class="c"&gt;# show the dependency graph + commands, run nothing&lt;/span&gt;
kaupang rollback api &lt;span class="nt"&gt;--target&lt;/span&gt; prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multi-backend from one config — Compose, Swarm, or Kubernetes, same env definition.&lt;/li&gt;
&lt;li&gt;Immutable &amp;amp; auditable — every deploy resolves tags to a digest, pins it, and records the full artifact in a ledger. Staging validates a digest; prod redeploys that exact digest. rollback replays a known-good snapshot.&lt;/li&gt;
&lt;li&gt;Airgap-friendly — pack a "solution" with pinned digests into a portable OCI bundle, ship it over oras, and deploy it with no registry access on the far side.&lt;/li&gt;
&lt;li&gt;Composes with CI, doesn't compete — it's a push tool, not a reconciler. Your pipeline keeps triggers, approvals, and secrets; kaupang owns the deploy recipe.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;v0.1.0, MIT. Every deploy path is exercised end-to-end against real Docker and a kind cluster in CI. The Kubernetes backend is intentionally minimal (Namespace + Deployment + Service) — it's for straightforward services, not a Helm replacement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @kaupang/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo &amp;amp; docs: &lt;a href="https://github.com/kaupang-dev/kaupang" rel="noopener noreferrer"&gt;https://github.com/kaupang-dev/kaupang&lt;/a&gt; — feedback and issues very welcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>node</category>
    </item>
  </channel>
</rss>
