Bunch more stability fixes

This commit is contained in:
2026-03-23 22:12:24 -06:00
parent a56f8446f7
commit 63e05adcee
268 changed files with 20626 additions and 2260 deletions

View File

@@ -13,7 +13,7 @@
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/docs/getting-started/installation/">
<link rel="canonical" href="https://cmlite.org/docs/getting-started/installation/">
<link rel="prev" href="../">
@@ -26,7 +26,7 @@
<link rel="icon" href="../../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.2">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
@@ -130,7 +130,7 @@
})();
</script>
<script>"undefined"!=typeof __md_analytics&&__md_analytics()</script>
<script>if("undefined"!=typeof __md_analytics){var consent=__md_get("__consent");consent&&consent.analytics&&__md_analytics()}</script>
@@ -139,15 +139,15 @@
<meta property="og:type" content="website" />
<meta property="og:title" content="Installation - Changemaker Lite" />
<meta property="og:description" content="System requirements, configuration wizard walkthrough, and initial service startup." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/docs/getting-started/installation.png" />
<meta property="og:image" content="https://cmlite.org/assets/images/social/docs/getting-started/installation.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/docs/getting-started/installation/" />
<meta property="og:url" content="https://cmlite.org/docs/getting-started/installation/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Installation - Changemaker Lite" />
<meta property="twitter:description" content="System requirements, configuration wizard walkthrough, and initial service startup." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/docs/getting-started/installation.png" />
<meta property="twitter:image" content="https://cmlite.org/assets/images/social/docs/getting-started/installation.png" />
</head>
@@ -849,11 +849,11 @@
.cm-header-nav__hamburger { display: block; }
.cm-header-nav__dropdown-menu { display: none !important; }
}
/* Tell Material that sidebar sticky offset = tabs height (blue header scrolls away) */
/* Sidebar sticky offset = 0 since blue header scrolls away */
:root {
--md-header-height: 0px;
}
/* Hidden Material header — 0 height but search children overflow visibly */
/* Hidden Material header — keeps search anchored near tabs */
.md-header--cm-hidden {
height: 0 !important;
min-height: 0 !important;
@@ -944,7 +944,7 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
height: 0 !important;
overflow: hidden !important;
}
/* Material tabs: sticky at top (blue header scrolls away, tabs persist) */
/* Material tabs: sticky at viewport top when blue header scrolls away */
.md-tabs {
position: sticky;
top: 0;
@@ -1483,6 +1483,56 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pre-built-image-installation" class="md-nav__link">
<span class="md-ellipsis">
Pre-built Image Installation
</span>
</a>
<nav class="md-nav" aria-label="Pre-built Image Installation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#one-line-install" class="md-nav__link">
<span class="md-ellipsis">
One-Line Install
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#manual-download" class="md-nav__link">
<span class="md-ellipsis">
Manual Download
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-different-from-source-install" class="md-nav__link">
<span class="md-ellipsis">
What's Different from Source Install
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -1891,6 +1941,14 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
</span>
<span class="md-status md-status--new" title="Recently added">
</span>
</a>
</li>
@@ -2494,6 +2552,8 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
@@ -2586,6 +2646,56 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pre-built-image-installation" class="md-nav__link">
<span class="md-ellipsis">
Pre-built Image Installation
</span>
</a>
<nav class="md-nav" aria-label="Pre-built Image Installation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#one-line-install" class="md-nav__link">
<span class="md-ellipsis">
One-Line Install
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#manual-download" class="md-nav__link">
<span class="md-ellipsis">
Manual Download
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-different-from-source-install" class="md-nav__link">
<span class="md-ellipsis">
What's Different from Source Install
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -2923,9 +3033,41 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<nav class="md-tags" >
<span class="md-tag">docker</span>
<span class="md-tag">getting-started</span>
<span class="md-tag">guide</span>
<span class="md-tag">operator</span>
</nav>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/docs/getting-started/installation.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/v2/mkdocs/docs/docs/getting-started/installation.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
@@ -2934,7 +3076,7 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/docs/getting-started/installation.md" title="View source of this page" class="md-content__button md-icon">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/v2/mkdocs/docs/docs/getting-started/installation.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
@@ -2971,6 +3113,71 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<p>If you used the wizard's generated password, change it immediately from the admin dashboard.</p>
</div>
<hr />
<h2 id="pre-built-image-installation">Pre-built Image Installation<a class="headerlink" href="#pre-built-image-installation" title="Permanent link">&para;</a></h2>
<p>For production deployments, you can skip cloning the source repository entirely. Pre-built Docker images are pulled from the Gitea container registry.</p>
<h3 id="one-line-install">One-Line Install<a class="headerlink" href="#one-line-install" title="Permanent link">&para;</a></h3>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>curl<span class="w"> </span>-fsSL<span class="w"> </span>https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh<span class="w"> </span><span class="p">|</span><span class="w"> </span>bash
</span></code></pre></div>
<p>This script:</p>
<ol>
<li>Checks prerequisites (Docker, Docker Compose, OpenSSL)</li>
<li>Downloads the latest release package from Gitea</li>
<li>Extracts to <code>~/changemaker.lite/</code></li>
<li>Launches the configuration wizard (<code>config.sh</code>)</li>
</ol>
<p>After the wizard completes, start everything with <code>docker compose up -d</code>.</p>
<h3 id="manual-download">Manual Download<a class="headerlink" href="#manual-download" title="Permanent link">&para;</a></h3>
<p>If you prefer not to pipe to bash:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="c1"># Download latest release</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a>curl<span class="w"> </span>-LO<span class="w"> </span>https://gitea.bnkops.com/admin/changemaker.lite/releases/latest/download/changemaker-lite-latest.tar.gz
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a>tar<span class="w"> </span>xzf<span class="w"> </span>changemaker-lite-latest.tar.gz
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a><span class="nb">cd</span><span class="w"> </span>changemaker-lite
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a>bash<span class="w"> </span>config.sh
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d
</span></code></pre></div>
<h3 id="whats-different-from-source-install">What's Different from Source Install<a class="headerlink" href="#whats-different-from-source-install" title="Permanent link">&para;</a></h3>
<table>
<thead>
<tr>
<th></th>
<th>Source Install</th>
<th>Pre-built Install</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Download size</strong></td>
<td>~200 MB (full repo)</td>
<td>~2 MB (config + scripts)</td>
</tr>
<tr>
<td><strong>First startup</strong></td>
<td>10+ min (TypeScript compile + Docker build)</td>
<td>~2 min (image pull only)</td>
</tr>
<tr>
<td><strong>Requires</strong></td>
<td>Git, full repo</td>
<td>Docker only</td>
</tr>
<tr>
<td><strong>Upgrades</strong></td>
<td><code>git pull</code> + rebuild</td>
<td>Download new release tarball</td>
</tr>
<tr>
<td><strong>Development</strong></td>
<td>Edit source, hot-reload</td>
<td>Not for development</td>
</tr>
</tbody>
</table>
<div class="admonition tip">
<p class="admonition-title">When to use which</p>
<p>Use <strong>pre-built install</strong> for production deployments and quick evaluation.
Use <strong>source install</strong> when you want to modify the platform code or contribute to development.</p>
</div>
<hr />
<h2 id="configuration-wizard-configsh">Configuration Wizard (<code>config.sh</code>)<a class="headerlink" href="#configuration-wizard-configsh" title="Permanent link">&para;</a></h2>
<p>The wizard performs <strong>14 steps</strong> to produce a fully configured <code>.env</code> file and prepare the system for startup. Each step is interactive with sensible defaults.</p>
<h3 id="step-1-prerequisites-check">Step 1: Prerequisites Check<a class="headerlink" href="#step-1-prerequisites-check" title="Permanent link">&para;</a></h3>
@@ -3071,8 +3278,8 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<tbody>
<tr>
<td>JWT &amp; Encryption</td>
<td>3</td>
<td><code>JWT_ACCESS_SECRET</code>, <code>JWT_REFRESH_SECRET</code>, <code>ENCRYPTION_KEY</code> (64-char hex)</td>
<td>4</td>
<td><code>JWT_ACCESS_SECRET</code>, <code>JWT_REFRESH_SECRET</code>, <code>JWT_INVITE_SECRET</code> (each 64-char hex), <code>ENCRYPTION_KEY</code> (64-char hex, must differ from JWT secrets)</td>
</tr>
<tr>
<td>Database</td>
@@ -3200,7 +3407,7 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<p>Complete tunnel setup is done from the admin GUI at <strong>Settings &gt; Tunnel</strong> after services are running.</p>
<h3 id="step-9-cors-origins">Step 9: CORS Origins<a class="headerlink" href="#step-9-cors-origins" title="Permanent link">&para;</a></h3>
<p>Automatically calculates allowed origins from your domain:</p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>http://app.DOMAIN,https://app.DOMAIN,http://DOMAIN,https://DOMAIN,http://localhost:3000,http://localhost,http://localhost:4003
<div class="language-text highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a>http://app.DOMAIN,https://app.DOMAIN,http://DOMAIN,https://DOMAIN,http://localhost:3000,http://localhost,http://localhost:4003
</span></code></pre></div>
<h3 id="step-10-nginx-config-generation">Step 10: Nginx Config Generation<a class="headerlink" href="#step-10-nginx-config-generation" title="Permanent link">&para;</a></h3>
<p>Renders all <code>.conf.template</code> files in <code>nginx/conf.d/</code> by substituting <code>${DOMAIN}</code> with your configured domain. This produces the nginx configuration files that handle subdomain routing.</p>
@@ -3315,19 +3522,20 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<hr />
<h2 id="manual-setup-alternative">Manual Setup (Alternative)<a class="headerlink" href="#manual-setup-alternative" title="Permanent link">&para;</a></h2>
<p>If you prefer to configure by hand instead of using the wizard:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a>cp<span class="w"> </span>.env.example<span class="w"> </span>.env
<div class="language-bash highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>cp<span class="w"> </span>.env.example<span class="w"> </span>.env
</span></code></pre></div>
<p>At minimum, set these required secrets:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="c1"># Generate cryptographic secrets</span>
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="nv">V2_POSTGRES_PASSWORD</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-base64<span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span>-dc<span class="w"> </span><span class="s1">&#39;a-zA-Z0-9&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span>-c<span class="w"> </span><span class="m">24</span><span class="k">)</span>
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="nv">REDIS_PASSWORD</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-base64<span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span>-dc<span class="w"> </span><span class="s1">&#39;a-zA-Z0-9&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span>-c<span class="w"> </span><span class="m">24</span><span class="k">)</span>
</span><span id="__span-5-4"><a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="nv">JWT_ACCESS_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
</span><span id="__span-5-5"><a id="__codelineno-5-5" name="__codelineno-5-5" href="#__codelineno-5-5"></a><span class="nv">JWT_REFRESH_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
</span><span id="__span-5-6"><a id="__codelineno-5-6" name="__codelineno-5-6" href="#__codelineno-5-6"></a><span class="nv">ENCRYPTION_KEY</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="c1"># Generate cryptographic secrets</span>
</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="nv">V2_POSTGRES_PASSWORD</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-base64<span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span>-dc<span class="w"> </span><span class="s1">&#39;a-zA-Z0-9&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span>-c<span class="w"> </span><span class="m">24</span><span class="k">)</span>
</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="nv">REDIS_PASSWORD</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-base64<span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span>-dc<span class="w"> </span><span class="s1">&#39;a-zA-Z0-9&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span>-c<span class="w"> </span><span class="m">24</span><span class="k">)</span>
</span><span id="__span-7-4"><a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="nv">JWT_ACCESS_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
</span><span id="__span-7-5"><a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="nv">JWT_REFRESH_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
</span><span id="__span-7-6"><a id="__codelineno-7-6" name="__codelineno-7-6" href="#__codelineno-7-6"></a><span class="nv">JWT_INVITE_SECRET</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span>
</span><span id="__span-7-7"><a id="__codelineno-7-7" name="__codelineno-7-7" href="#__codelineno-7-7"></a><span class="nv">ENCRYPTION_KEY</span><span class="o">=</span><span class="k">$(</span>openssl<span class="w"> </span>rand<span class="w"> </span>-hex<span class="w"> </span><span class="m">32</span><span class="k">)</span><span class="w"> </span><span class="c1"># must differ from all JWT secrets</span>
</span></code></pre></div>
<p>Set your admin credentials (password must meet the 12+ char complexity requirement):</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="nv">INITIAL_ADMIN_EMAIL</span><span class="o">=</span>admin@yourdomain.org
</span><span id="__span-6-2"><a id="__codelineno-6-2" name="__codelineno-6-2" href="#__codelineno-6-2"></a><span class="nv">INITIAL_ADMIN_PASSWORD</span><span class="o">=</span>YourStrongPassword1
<div class="language-bash highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="nv">INITIAL_ADMIN_EMAIL</span><span class="o">=</span>admin@yourdomain.org
</span><span id="__span-8-2"><a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="nv">INITIAL_ADMIN_PASSWORD</span><span class="o">=</span>YourStrongPassword1
</span></code></pre></div>
<p>Then configure optional sections:</p>
<ul>
@@ -3340,7 +3548,7 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<hr />
<h2 id="full-stack-startup">Full Stack Startup<a class="headerlink" href="#full-stack-startup" title="Permanent link">&para;</a></h2>
<p>After configuration, start the entire platform:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d
<div class="language-bash highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d
</span></code></pre></div>
<p>That's it. Docker handles the startup order automatically:</p>
<ol>
@@ -3351,40 +3559,40 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<li><strong>Init containers</strong> (nocodb-init, listmonk-init, etc.) run once and exit</li>
</ol>
<p>Watch the startup progress:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>-f<span class="w"> </span>api<span class="w"> </span>--tail<span class="w"> </span><span class="m">20</span>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>-f<span class="w"> </span>api<span class="w"> </span>--tail<span class="w"> </span><span class="m">20</span>
</span></code></pre></div>
<p>Once you see <code>Starting server on port 4000</code>, open <strong>http://localhost:3000</strong> and log in.</p>
<h3 id="include-monitoring">Include Monitoring<a class="headerlink" href="#include-monitoring" title="Permanent link">&para;</a></h3>
<p>The monitoring stack (Prometheus, Grafana, Alertmanager) uses a Docker Compose profile and isn't included by default:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>--profile<span class="w"> </span>monitoring<span class="w"> </span>up<span class="w"> </span>-d
<div class="language-bash highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>--profile<span class="w"> </span>monitoring<span class="w"> </span>up<span class="w"> </span>-d
</span></code></pre></div>
<h3 id="start-only-core-services">Start Only Core Services<a class="headerlink" href="#start-only-core-services" title="Permanent link">&para;</a></h3>
<p>If you prefer a minimal startup (lower resource usage):</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>v2-postgres<span class="w"> </span>redis<span class="w"> </span>api<span class="w"> </span>admin<span class="w"> </span>nginx
<div class="language-bash highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>v2-postgres<span class="w"> </span>redis<span class="w"> </span>api<span class="w"> </span>admin<span class="w"> </span>nginx
</span></code></pre></div>
<div class="admonition note">
<p class="admonition-title">Manual migrations</p>
<p>The API container runs migrations and seeding automatically on startup via its
entrypoint script. You only need to run them manually if you're developing
locally without Docker:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="nb">cd</span><span class="w"> </span>api<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npx<span class="w"> </span>prisma<span class="w"> </span>migrate<span class="w"> </span>deploy<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npx<span class="w"> </span>prisma<span class="w"> </span>db<span class="w"> </span>seed
<div class="language-bash highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a><span class="nb">cd</span><span class="w"> </span>api<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npx<span class="w"> </span>prisma<span class="w"> </span>migrate<span class="w"> </span>deploy<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npx<span class="w"> </span>prisma<span class="w"> </span>db<span class="w"> </span>seed
</span></code></pre></div>
</div>
<p>See <a href="../services/">Services Overview</a> for the complete service catalog.</p>
<hr />
<h2 id="verifying-installation">Verifying Installation<a class="headerlink" href="#verifying-installation" title="Permanent link">&para;</a></h2>
<p>After starting services, verify everything is healthy:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a><span class="c1"># Check running containers</span>
</span><span id="__span-12-2"><a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>ps
</span><span id="__span-12-3"><a id="__codelineno-12-3" name="__codelineno-12-3" href="#__codelineno-12-3"></a>
</span><span id="__span-12-4"><a id="__codelineno-12-4" name="__codelineno-12-4" href="#__codelineno-12-4"></a><span class="c1"># API health check</span>
</span><span id="__span-12-5"><a id="__codelineno-12-5" name="__codelineno-12-5" href="#__codelineno-12-5"></a>curl<span class="w"> </span>-s<span class="w"> </span>http://localhost:4000/api/health<span class="w"> </span><span class="p">|</span><span class="w"> </span>python3<span class="w"> </span>-m<span class="w"> </span>json.tool
</span><span id="__span-12-6"><a id="__codelineno-12-6" name="__codelineno-12-6" href="#__codelineno-12-6"></a>
</span><span id="__span-12-7"><a id="__codelineno-12-7" name="__codelineno-12-7" href="#__codelineno-12-7"></a><span class="c1"># View API logs</span>
</span><span id="__span-12-8"><a id="__codelineno-12-8" name="__codelineno-12-8" href="#__codelineno-12-8"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>api<span class="w"> </span>--tail<span class="w"> </span><span class="m">20</span>
</span><span id="__span-12-9"><a id="__codelineno-12-9" name="__codelineno-12-9" href="#__codelineno-12-9"></a>
</span><span id="__span-12-10"><a id="__codelineno-12-10" name="__codelineno-12-10" href="#__codelineno-12-10"></a><span class="c1"># Check for containers in restart loops</span>
</span><span id="__span-12-11"><a id="__codelineno-12-11" name="__codelineno-12-11" href="#__codelineno-12-11"></a>docker<span class="w"> </span>compose<span class="w"> </span>ps<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>restarting
<div class="language-bash highlight"><pre><span></span><code><span id="__span-14-1"><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a><span class="c1"># Check running containers</span>
</span><span id="__span-14-2"><a id="__codelineno-14-2" name="__codelineno-14-2" href="#__codelineno-14-2"></a>docker<span class="w"> </span>compose<span class="w"> </span>ps
</span><span id="__span-14-3"><a id="__codelineno-14-3" name="__codelineno-14-3" href="#__codelineno-14-3"></a>
</span><span id="__span-14-4"><a id="__codelineno-14-4" name="__codelineno-14-4" href="#__codelineno-14-4"></a><span class="c1"># API health check</span>
</span><span id="__span-14-5"><a id="__codelineno-14-5" name="__codelineno-14-5" href="#__codelineno-14-5"></a>curl<span class="w"> </span>-s<span class="w"> </span>http://localhost:4000/api/health<span class="w"> </span><span class="p">|</span><span class="w"> </span>python3<span class="w"> </span>-m<span class="w"> </span>json.tool
</span><span id="__span-14-6"><a id="__codelineno-14-6" name="__codelineno-14-6" href="#__codelineno-14-6"></a>
</span><span id="__span-14-7"><a id="__codelineno-14-7" name="__codelineno-14-7" href="#__codelineno-14-7"></a><span class="c1"># View API logs</span>
</span><span id="__span-14-8"><a id="__codelineno-14-8" name="__codelineno-14-8" href="#__codelineno-14-8"></a>docker<span class="w"> </span>compose<span class="w"> </span>logs<span class="w"> </span>api<span class="w"> </span>--tail<span class="w"> </span><span class="m">20</span>
</span><span id="__span-14-9"><a id="__codelineno-14-9" name="__codelineno-14-9" href="#__codelineno-14-9"></a>
</span><span id="__span-14-10"><a id="__codelineno-14-10" name="__codelineno-14-10" href="#__codelineno-14-10"></a><span class="c1"># Check for containers in restart loops</span>
</span><span id="__span-14-11"><a id="__codelineno-14-11" name="__codelineno-14-11" href="#__codelineno-14-11"></a>docker<span class="w"> </span>compose<span class="w"> </span>ps<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>restarting
</span></code></pre></div>
<p>You should see the API return <code>{"status":"ok"}</code> and all started containers in a "running" state.</p>
<hr />
@@ -3417,6 +3625,8 @@ locally without Docker:</p>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
@@ -3475,7 +3685,7 @@ locally without Docker:</p>
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
Copyright &copy; 20242026 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
@@ -3490,7 +3700,7 @@ locally without Docker:</p>
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M80 104a24 24 0 1 0 0-48 24 24 0 1 0 0 48m80-24c0 32.8-19.7 61-48 73.3V224h176c26.5 0 48-21.5 48-48v-22.7c-28.3-12.3-48-40.5-48-73.3 0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V176c0 61.9-50.1 112-112 112H112v70.7c28.3 12.3 48 40.5 48 73.3 0 44.2-35.8 80-80 80S0 476.2 0 432c0-32.8 19.7-61 48-73.3V153.4C19.7 141 0 112.8 0 80 0 35.8 35.8 0 80 0s80 35.8 80 80m232 0a24 24 0 1 0-48 0 24 24 0 1 0 48 0M80 456a24 24 0 1 0 0-48 24 24 0 1 0 0 48"/></svg>
</a>
@@ -3512,11 +3722,78 @@ locally without Docker:</p>
<div class="md-dialog__inner md-typeset"></div>
</div>
<div class="md-progress" data-md-component="progress" role="progressbar"></div>
<div class="md-consent" data-md-component="consent" id="__consent" hidden>
<div class="md-consent__overlay"></div>
<aside class="md-consent__inner">
<form class="md-consent__form md-grid md-typeset" name="consent">
<h4>Cookie consent</h4>
<p>We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation. With your consent, you help us improve.
</p>
<input class="md-toggle" type="checkbox" id="__settings" >
<div class="md-consent__settings">
<ul class="task-list">
<li class="task-list-item">
<label class="task-list-control">
<input type="checkbox" name="analytics" checked>
<span class="task-list-indicator"></span>
Google Analytics
</label>
</li>
</ul>
</div>
<div class="md-consent__controls">
<button class="md-button md-button--primary">Accept</button>
<button type="reset" class="md-button md-button--primary">Reject</button>
<label class="md-button" for="__settings">Manage settings</label>
</div>
</form>
</aside>
</div>
<script>var consent=__md_get("__consent");if(consent)for(var input of document.forms.consent.elements)input.name&&(input.checked=consent[input.name]||!1);else"file:"!==location.protocol&&setTimeout((function(){document.querySelector("[data-md-component=consent]").hidden=!1}),250);var form=document.forms.consent;for(var action of["submit","reset"])form.addEventListener(action,(function(e){if(e.preventDefault(),"reset"===e.type)for(var n of document.forms.consent.elements)n.name&&(n.checked=!1);__md_set("__consent",Object.fromEntries(Array.from(new FormData(form).keys()).map((function(e){return[e,!0]})))),location.hash="",location.reload()}))</script>
<script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.code.select", "content.tabs.link", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.instant", "navigation.instant.prefetch", "navigation.instant.progress", "navigation.path", "navigation.prune", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../assets/javascripts/bundle.79ae519e.min.js"></script>