<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>bitExpert Blog</title>
        <link>https://blog.bitexpert.de/blog</link>
        <description>bitExpert Blog</description>
        <lastBuildDate>Thu, 01 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 bitExpert AG.</copyright>
        <item>
            <title><![CDATA[PHPUGMRN 05/26 meetup (Symfony meetup)]]></title>
            <link>https://blog.bitexpert.de/blog/phpugmrn_october_2026</link>
            <guid>https://blog.bitexpert.de/blog/phpugmrn_october_2026</guid>
            <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Join the PHP Usergroup Rhein-Neckar on October 22nd for a Symfony special meetup, featuring expert talks on building adaptable applications and applying software engineering best practices to AI-powered tools.]]></description>
            <content:encoded><![CDATA[<p>Join the <a href="https://www.meetup.com/phpug-rhein-neckar/" target="_blank" rel="noopener noreferrer" class="">PHP Usergroup Rhein-Neckar metropolitan region</a> on October 22nd for a special Symfony meetup, in cooperation with <a href="https://sensiolabs.com/de/" target="_blank" rel="noopener noreferrer" class="">SensioLabs Deutschland</a>.</p>
<!-- -->
<p>Alexander M. Turek will share his insights on designing <a href="https://symfony.com/" target="_blank" rel="noopener noreferrer" class="">Symfony applications</a> that can adapt to change without major disruptions.
He will explore architectural choices that keep core business logic stable while technology evolves, giving your application a powerful long-term advantage.</p>
<p>Stiven Llupa will discuss how to apply traditional software engineering principles to AI-powered platforms that offer limited support for these practices.
He will take you through a real-world example, following a small function from start to finish, to demonstrate the challenges and opportunities of working with these platforms.</p>
<p>Interested in attending? Simply sign up on our <a href="https://c.sensiolabs.de/events/symfony-meetup-mannheim-2026" target="_blank" rel="noopener noreferrer" class="">meetup page</a> to secure your spot!</p>]]></content:encoded>
            <category>PHP</category>
            <category>Usergroup</category>
            <category>MRN</category>
            <category>phpugmrn</category>
            <category>Symfony</category>
        </item>
        <item>
            <title><![CDATA[Symfony Security Auditor]]></title>
            <link>https://blog.bitexpert.de/blog/symfony_security_auditor</link>
            <guid>https://blog.bitexpert.de/blog/symfony_security_auditor</guid>
            <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[ Discover how to identify and fix security vulnerabilities in your Symfony applications using the Symfony Security Auditor, a powerful tool that provides detailed reports and actionable insights to improve your application's security.]]></description>
            <content:encoded><![CDATA[<p>I recently discovered the <a href="https://github.com/vinceAmstoutz/symfony-security-auditor" target="_blank" rel="noopener noreferrer" class="">Symfony Security Auditor</a> project on GitHub. As security is more crucial than ever, I decided to give it a try.</p>
<!-- -->
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-the-symfony-security-auditor">What is the Symfony Security Auditor?<a href="https://blog.bitexpert.de/blog/symfony_security_auditor#what-is-the-symfony-security-auditor" class="hash-link" aria-label="Direct link to What is the Symfony Security Auditor?" title="Direct link to What is the Symfony Security Auditor?" translate="no">​</a></h3>
<p>The Symfony Security Auditor is a multi-agent security auditor for Symfony applications. It uses an adversarial Attacker and Reviewer loop to catch application-level flaws that other tools might miss.
Built on top of the <a href="https://github.com/symfony/ai" target="_blank" rel="noopener noreferrer" class="">symfony/ai</a> platform, it's provider-agnostic, which is a significant advantage for us since we self-host models or use those provided by the <a href="https://cloud.ionos.com/managed/ai-model-hub" target="_blank" rel="noopener noreferrer" class="">IONOS AI Model Hub</a>.</p>
<p>The Auditor consists of two parts:</p>
<ul>
<li class="">An adversarial Attacker agent that hunts for vulnerabilities.</li>
<li class="">A skeptical Reviewer agent that culls false positives over up to three iterations and then emits a validated report in various formats (console, JSON, SARIF, HTML, or Markdown).</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-install">How to install<a href="https://blog.bitexpert.de/blog/symfony_security_auditor#how-to-install" class="hash-link" aria-label="Direct link to How to install" title="Direct link to How to install" translate="no">​</a></h3>
<p>You can install the Auditor as a standalone tool or add it as a dev dependency to your project. For testing purposes, I chose the latter.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">composer</span><span class="token plain"> require </span><span class="token parameter variable" style="color:#36acaa">--dev</span><span class="token plain"> vinceamstoutz/symfony-security-auditor</span><br></div></code></pre></div></div>
<p>Since we run our own AI gateway, I also installed the <code>symfony/ai-generic-platform</code> bundle to ensure the Auditor can communicate properly with our AI gateway.</p>
<p>Next, I configured the AI gateway URL and access token in the <code>config/packages/ai_generic_platform.yaml</code> config file:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">ai</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">platform</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">generic</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">default</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token key atrule" style="color:#00a4db">base_url</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'https://ai-gateway.loc'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token key atrule" style="color:#00a4db">api_key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-random-api-key'</span><br></div></code></pre></div></div>
<p>Note that the base URL should not include the <code>/v1</code> postfix, just the base url.</p>
<p>I also configured which model to use in the <code>config/packages/symfony_security_auditor.yaml</code> file:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">when@dev</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token important">&amp;symfony_security_auditor</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">symfony_security_auditor</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'qwen3.8-28b'</span><br></div></code></pre></div></div>
<p>For my first test, I used the Qwen 3.8 28B model, which worked fine.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-run-the-auditor">How to run the Auditor<a href="https://blog.bitexpert.de/blog/symfony_security_auditor#how-to-run-the-auditor" class="hash-link" aria-label="Direct link to How to run the Auditor" title="Direct link to How to run the Auditor" translate="no">​</a></h3>
<p>Once everything is configured, you can run the Auditor with this command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">./bin/console audit:run </span><span class="token parameter variable" style="color:#36acaa">--format</span><span class="token plain"> markdown </span><span class="token parameter variable" style="color:#36acaa">--output</span><span class="token plain"> report.md</span><br></div></code></pre></div></div>
<p>Running the Auditor takes some time, depending on your project size and complexity.
My first test ran for about 35 minutes.</p>
<p>The generated Markdown file contains all findings ranked by severity (e.g., critical, high, medium, or low). A finding looks like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">### 🟠 HIGH — Open redirect in OAuthAuthenticator base class (third occurrence)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- **Type:** `open_redirect` (OWASP A01:2025 - Broken Access Control, CWE-601)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- **Location:** `src/Security/OAuth/OAuthAuthenticator.php:43-48`</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- **Confidence:** 70%</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OAuthAuthenticator::onAuthenticationSuccess retrieves the target path from the session using getTargetPath()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">and returns a RedirectResponse to it without validation. Since OAuth flows are triggered via route matching</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">and may involve user redirection after login, an attacker could manipulate the _target_path parameter to </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">redirect the user to an external site after OAuth authentication succeeds.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">**Vulnerable code:**</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if (($targetPath = $this-&gt;getTargetPath($request-&gt;getSession(), $firewallName)) !== null) {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  return new RedirectResponse($targetPath);</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">return new RedirectResponse($this-&gt;router-&gt;generate('app_index'));</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">**Attack vector:** </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. Attacker initiates OAuth flow with a manipulated redirect state (e.g., via session poisoning or pre-filled _target_path).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. User completes OAuth authentication.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. OAuthAuthenticator::onAuthenticationSuccess retrieves the unsanitized $targetPath.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4. User is redirected to an external domain, enabling phishing or session theft.</span><br></div></code></pre></div></div>
<p>You can then feed these findings into your coding agent and let the agent work on this task.</p>
<p>You can then feed these findings into your coding agent and let it work on this task.</p>
<p>After running the Auditor multiple times, I encountered some <a href="https://github.com/vinceAmstoutz/symfony-security-auditor/issues/372" target="_blank" rel="noopener noreferrer" class="">OpenSSL SSL_read</a>.
I prepared a <a href="https://github.com/vinceAmstoutz/symfony-security-auditor/pull/375" target="_blank" rel="noopener noreferrer" class="">Pull Request</a> with some changes and increased the retry limit (symfony_security_auditor.audit.retry.max_attempts) to 5 to make the Auditor work again.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.bitexpert.de/blog/symfony_security_auditor#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h3>
<p>The initial report proved to be very valuable. I ran it against a legacy code base, and it was helpful to get these findings back.
Feeding the results into my coding agent (running a different model) and letting the coding agent judge and provide feedback helped understand which of the mentioned issues needed to be solved and which were reported as false positives.</p>]]></content:encoded>
            <category>PHP</category>
            <category>Symfony</category>
            <category>Security</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[Hot reload with FrankenPHP]]></title>
            <link>https://blog.bitexpert.de/blog/frankenphp_hot_reload</link>
            <guid>https://blog.bitexpert.de/blog/frankenphp_hot_reload</guid>
            <pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to streamline your PHP development workflow with FrankenPHP's hot reload feature, which automatically refreshes your frontend when files change, simplifying development and improving productivity.]]></description>
            <content:encoded><![CDATA[<p>While preparing the slides for my talk about <a href="https://www.frankenphp.dev/" target="_blank" rel="noopener noreferrer" class="">FrankenPHP</a> and <a href="https://www.ddev.com/" target="_blank" rel="noopener noreferrer" class="">DDEV</a> at the <a href="https://api-platform.com/con/2026/" target="_blank" rel="noopener noreferrer" class="">API Platform Conference 2026</a>, I discovered the hot reload feature of FrankenPHP.</p>
<!-- -->
<p>In modern JavaScript tooling, <a href="https://beyondthecode.medium.com/understanding-hot-module-replacement-hmr-f13f66cb5ada" target="_blank" rel="noopener noreferrer" class="">Hot Module Replacement (HMR)</a> is the standard.
The frontend refreshes automatically when files change, simplifying development.</p>
<p>In traditional PHP, we didn't have this workflow, but with FrankenPHP's hot reloading, the server watches for filesystem changes and pushes <a href="https://mercure.rocks/" target="_blank" rel="noopener noreferrer" class="">Mercure</a> updates to the browser.
Depending on your setup, the DOM will be morphed (this needs <a href="https://github.com/bigskysoftware/idiomorph" target="_blank" rel="noopener noreferrer" class="">Idiomorph</a> to be available) or as fallback, the page gets reloaded.</p>
<p><em>Important:</em> Hot reload is for development environments only and should not be enabled in production, as it's not secure and slows down the application.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-enable-hot-reload-server-side">How to enable Hot Reload (server side)<a href="https://blog.bitexpert.de/blog/frankenphp_hot_reload#how-to-enable-hot-reload-server-side" class="hash-link" aria-label="Direct link to How to enable Hot Reload (server side)" title="Direct link to How to enable Hot Reload (server side)" translate="no">​</a></h3>
<p>To enable the hot reload feature in FrankenPHP, you need to:</p>
<ol>
<li class="">Enable Mercure in your Caddyfile:</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mercure {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    anonymous</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<ol start="2">
<li class="">Enable <code>hot_reload</code> in the <code>php_server</code> configuration section:</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">php_server {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    hot_reload</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>By default, the server process watches all files with specific extensions <code>./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}</code> in the current working directory.</p>
<p>You can explicitly configure a file list by passing a list to the <code>hot_reload</code> configuration:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">php_server {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    hot_reload src/**/*{.php,.js} config/**/*.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-enable-hot-reload-client-side">How to enable Hot Reload (client side)<a href="https://blog.bitexpert.de/blog/frankenphp_hot_reload#how-to-enable-hot-reload-client-side" class="hash-link" aria-label="Direct link to How to enable Hot Reload (client side)" title="Direct link to How to enable Hot Reload (client side)" translate="no">​</a></h3>
<p>To make the browser aware of hot reload, include the following snippet in your templates:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="generic-php-application">Generic PHP Application<a href="https://blog.bitexpert.de/blog/frankenphp_hot_reload#generic-php-application" class="hash-link" aria-label="Direct link to Generic PHP Application" title="Direct link to Generic PHP Application" translate="no">​</a></h4>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token delimiter important">&lt;?php</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">isset</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">$_SERVER</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string single-quoted-string" style="color:#e3116c">'FRANKENPHP_HOT_RELOAD'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">meta name</span><span class="token operator" style="color:#393A34">=</span><span class="token string double-quoted-string" style="color:#e3116c">"frankenphp-hot-reload:url"</span><span class="token plain"> content</span><span class="token operator" style="color:#393A34">=</span><span class="token string double-quoted-string" style="color:#e3116c">"&lt;?=</span><span class="token string double-quoted-string interpolation variable" style="color:#36acaa">$_SERVER</span><span class="token string double-quoted-string interpolation punctuation" style="color:#393A34">[</span><span class="token string double-quoted-string interpolation string single-quoted-string" style="color:#e3116c">'FRANKENPHP_HOT_RELOAD'</span><span class="token string double-quoted-string interpolation punctuation" style="color:#393A34">]</span><span class="token string double-quoted-string" style="color:#e3116c">?&gt;"</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">script src</span><span class="token operator" style="color:#393A34">=</span><span class="token string double-quoted-string" style="color:#e3116c">"https://cdn.jsdelivr.net/npm/idiomorph/dist/idiomorph.min.js"</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">script</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">script src</span><span class="token operator" style="color:#393A34">=</span><span class="token string double-quoted-string" style="color:#e3116c">"https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm"</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">=</span><span class="token string double-quoted-string" style="color:#e3116c">"module"</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">script</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">php </span><span class="token keyword" style="color:#00009f">endif</span><span class="token plain"> </span><span class="token delimiter important">?&gt;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="symfonytwig">Symfony/Twig<a href="https://blog.bitexpert.de/blog/frankenphp_hot_reload#symfonytwig" class="hash-link" aria-label="Direct link to Symfony/Twig" title="Direct link to Symfony/Twig" translate="no">​</a></h4>
<div class="language-twig codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-twig codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">{% if app.request.server.has('FRANKENPHP_HOT_RELOAD') %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;meta name="frankenphp-hot-reload:url" content="{{ app.request.server.get('FRANKENPHP_HOT_RELOAD') }}"&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;script src="https://cdn.jsdelivr.net/npm/idiomorph/dist/idiomorph.min.js"&gt;&lt;/script&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"&gt;&lt;/script&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{% endif %}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="sylius">Sylius<a href="https://blog.bitexpert.de/blog/frankenphp_hot_reload#sylius" class="hash-link" aria-label="Direct link to Sylius" title="Direct link to Sylius" translate="no">​</a></h4>
<p>Add this to your <code>templates/shop/javascripts.html.twig</code> template file:</p>
<div class="language-twig codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-twig codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">{% if app.request.server.has('FRANKENPHP_HOT_RELOAD') %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;meta name="frankenphp-hot-reload:url" content="{{ app.request.server.get('FRANKENPHP_HOT_RELOAD') }}"&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;script src="https://cdn.jsdelivr.net/npm/idiomorph/dist/idiomorph.min.js"&gt;&lt;/script&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    &lt;script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"&gt;&lt;/script&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{% endif %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{{ encore_entry_script_tags('app-shop-entry', null, 'app.shop') }}</span><br></div></code></pre></div></div>
<p>In conclusion, FrankenPHP's hot reload feature simplifies development by automatically refreshing the frontend when files change.
Remember to only use this feature in development environments, as it's not secure for production.</p>]]></content:encoded>
            <category>FrankenPHP</category>
            <category>Symfony</category>
            <category>Sylius</category>
        </item>
        <item>
            <title><![CDATA[Traefik OIDC setup]]></title>
            <link>https://blog.bitexpert.de/blog/traefik_oidc</link>
            <guid>https://blog.bitexpert.de/blog/traefik_oidc</guid>
            <pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to secure your Traefik Proxy with the Traefik OIDC plugin, a straightforward solution that integrates easily with your existing setup and supports a wide range of Identity Providers.]]></description>
            <content:encoded><![CDATA[<p>I recently worked on a project where we needed to secure our application by requiring users to authenticate against an Identity Provider before accessing it.</p>
<!-- -->
<p>To avoid adding extra components to our stack, I looked for a solution that would integrate easily with our existing <a href="https://www.traefik.io/" target="_blank" rel="noopener noreferrer" class="">Traefik Proxy</a> setup.
I came across the Traefik <a href="https://github.com/lukaszraczylo/traefikoidc" target="_blank" rel="noopener noreferrer" class="">OIDC plugin</a> which supports a wide range of Identity Providers, including Google, Azure, Auth0, Okta, Keycloak, Cognito, GitLab, and GitHub, making it perfect for our use case.</p>
<p>To install the plugin, add the following lines to your static Traefik configuration:</p>
<div class="language-YAML language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">experimental</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">plugins</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">traefikoidc</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">moduleName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> github.com/lukaszraczylo/traefikoidc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1.0.29</span><br></div></code></pre></div></div>
<p>In your dynamic Traefik configuration, add the plugin configuration for the <code>traefikoidc</code> middleware that will be referenced later in your application Traefik configuration.
In case of Entra ID as Identity Provider, configure your TenantId, ClientId, Client Secret, and the session encryption key accordingly:</p>
<div class="language-YAML language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">http</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">middlewares</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">entra-oidc</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">plugin</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">traefikoidc</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">providerURL</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://login.microsoftonline.com/&lt;TENANT_ID&gt;/v2.0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">clientID</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">clientSecret</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">sessionEncryptionKey</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">callbackURL</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/oauth2/callback"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">logoutURL</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/oauth2/logout"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">postLogoutRedirectURI</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">forceHTTPS</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">sessionMaxAge</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">28800</span><br></div></code></pre></div></div>
<p>Now, in your application configuration for Traefik, add the label referencing the <code>entra-oidc@file</code> middleware defined above and Traefik will handle the whole authentication flow for you:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">traefik.http.routers.my-app.middlewares=entra-oidc@file</span><br></div></code></pre></div></div>
<p>The whole Docker service definition looks like this:</p>
<div class="language-YAML language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">my-app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> traefik/whoami</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">expose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"80"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.enable=true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.my-app.rule=Host(`foo.bar`)"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.my-app.entrypoints=websecure"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.my-app.tls=true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.my-app.tls.certresolver=letsencrypt"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.routers.my-app.middlewares=entra-oidc@file"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"traefik.http.services.my-app.loadbalancer.server.port=80"</span><br></div></code></pre></div></div>
<p>Using the Traefik <a href="https://github.com/lukaszraczylo/traefikoidc" target="_blank" rel="noopener noreferrer" class="">OIDC plugin</a> made perfect sense for us.
It was easy to integrate, supports a variety of Identity Providers, and allows us to still rely on Traefik for all routing rules.
This solution efficiently secures our application without adding unnecessary complexity to our stack.</p>]]></content:encoded>
            <category>Devops</category>
            <category>Traefik</category>
        </item>
        <item>
            <title><![CDATA[Running Qwen 3.8 on Hashicorp Nomad]]></title>
            <link>https://blog.bitexpert.de/blog/nomad_qwen_3_8</link>
            <guid>https://blog.bitexpert.de/blog/nomad_qwen_3_8</guid>
            <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to efficiently deploy the Qwen 3.8 model on NVIDIA DGX Spark using Hashicorp Nomad.]]></description>
            <content:encoded><![CDATA[<p>While exploring ways to run <a class="" href="https://blog.bitexpert.de/blog/nomad_nvidia_workload">Nvidia workloads on Nomad</a>, I stumbled upon an opportunity to upgrade our vLLM setup to support the newly released <a href="https://huggingface.co/unsloth/Qwen3.8-27B-NVFP4" target="_blank" rel="noopener noreferrer" class="">Qwen 3.8 model</a>. This involved reconfiguring our <a href="https://www.nvidia.com/de-de/products/workstations/dgx-spark/" target="_blank" rel="noopener noreferrer" class="">NVIDIA DGX Spark</a> server.</p>
<!-- -->
<p>By accident, I came across this <a href="https://github.com/MiaAI-Lab/Qwen3.8-27B-DGX-Spark-RTX-6000" target="_blank" rel="noopener noreferrer" class="">GitHub repo</a> that had already done the heavy lifting for fine-tuning Qwen 3.8 with vLLM on a NVIDIA DGX Spark. The task left to do was to convert the configuration from a shell script to our Nomad job file.</p>
<p>First, I launched a new Docker container and downloaded the Qwen 3.8 model from Hugging Face using the following commands:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:#36acaa">-it</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-v</span><span class="token plain"> /var/nomad/data/host_volumes/da12a803-152e-83d4-049d-81e11d4adf5a/:/opt/vllm nvcr.io/nvidia/vllm:26.06-py3 </span><span class="token function" style="color:#d73a49">bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">hf download unsloth--Qwen3.8-27B-NVFP4</span><br></div></code></pre></div></div>
<p>After completing the model download, I submitted the Nomad job file to our cluster:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">job </span><span class="token string" style="color:#e3116c">"vllm.prod"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">datacenters</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"dc1"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">type</span><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"service"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">node_pool</span><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpu"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  group </span><span class="token string" style="color:#e3116c">"vllm"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">count</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">network</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      port </span><span class="token string" style="color:#e3116c">"app"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">to</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">host_network</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"vpn"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    volume </span><span class="token string" style="color:#e3116c">"vllm.prod"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">type</span><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"host"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">source</span><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"vllm.prod"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">access_mode</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"single-node-writer"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">attachment_mode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"file-system"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">restart</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">attempts</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">interval</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"30m"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">delay</span><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"45s"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">mode</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fail"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    task </span><span class="token string" style="color:#e3116c">"server"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">driver</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docker"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">vault</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">change_mode</span><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"signal"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">change_signal</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SIGUSR1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">template</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">data</span><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token heredoc string" style="color:#e3116c">&lt;&lt;EOH</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">HUGGING_FACE_HUB_TOKEN=YOUR_TOKEN_HERE</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">HF_HOME=/opt/vllm/hf</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">HF_HUB_CACHE=/opt/vllm/hf/hub</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">VLLM_FLOAT32_MATMUL_PRECISION=high</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">VLLM_ALLOW_LONG_MAX_MODEL_LEN=1</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">UCX_RCACHE_MAX_UNRELEASED=1024</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">CUTE_DSL_ARCH=sm_121a</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">TRITON_CACHE_DIR=/opt/vllm/.triton</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">EOH</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">destination</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".env"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">env</span><span class="token plain">         </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">config</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">image</span><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"nvcr.io/nvidia/vllm:26.06-py3"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">force_pull</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">ports</span><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"app"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">args</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"vllm"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"serve"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"/opt/nim/.cache/hf/hub/models--unsloth--Qwen3.8-27B-NVFP4/snapshots/7d6f8d4d72f56b92b3cdbf22f156b90e1bab0108"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--served-model-name"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"Qwen/Qwen3.8"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--tensor-parallel-size"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"1"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--quantization"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"compressed-tensors"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--attention-backend"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"triton_attn"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--trust-remote-code"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--enable-auto-tool-choice"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--tool-call-parser"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"qwen3_coder"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--reasoning-parser"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"qwen3"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--skip-mm-profiling"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--default-chat-template-kwargs"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          jsonencode(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">enable_thinking</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--enable-prefix-caching"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--enable-chunked-prefill"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--block-size"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"64"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--gpu_memory_utilization"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"0.84"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--max-num-seqs"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"4"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--max-num-batched-tokens"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"8192"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--max-model-len"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"1000000"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--speculative-config"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          jsonencode(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"method"</span><span class="token plain">: </span><span class="token string" style="color:#e3116c">"mtp"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"num_speculative_tokens"</span><span class="token plain">: </span><span class="token number" style="color:#36acaa">2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--hf-overrides"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          jsonencode(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"text_config"</span><span class="token plain">: </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token property" style="color:#36acaa">"rope_parameters"</span><span class="token plain">: </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"mrope_interleaved"</span><span class="token plain">: </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"mrope_section"</span><span class="token plain">: </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">11</span><span class="token plain">, </span><span class="token number" style="color:#36acaa">11</span><span class="token plain">, </span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"rope_type"</span><span class="token plain">: </span><span class="token string" style="color:#e3116c">"yarn"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"rope_theta"</span><span class="token plain">: </span><span class="token number" style="color:#36acaa">10000000</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"partial_rotary_factor"</span><span class="token plain">: </span><span class="token number" style="color:#36acaa">0.25</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"factor"</span><span class="token plain">: </span><span class="token number" style="color:#36acaa">4.0</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token property" style="color:#36acaa">"original_max_position_embeddings"</span><span class="token plain">: </span><span class="token number" style="color:#36acaa">262144</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"--api-key"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"some_api_key"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">runtime</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"nvidia"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">ipc_mode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"host"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">ulimit</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">memlock</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"-1:-1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">stack</span><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"67108864:67108864"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">volume_mount</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">volume</span><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"vllm.prod"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">destination</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/opt/vllm"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">read_only</span><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">resources</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">cpu</span><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">45000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">memory</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        device </span><span class="token string" style="color:#e3116c">"nvidia/gpu"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">count</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">service</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">name</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"vllm-prod"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">provider</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"nomad"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">port</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"app"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">check</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">name</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"alive"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">type</span><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tcp"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">interval</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"10s"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">timeout</span><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2s"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token keyword" style="color:#00009f">check_restart</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">limit</span><span class="token plain">           </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">grace</span><span class="token plain">           </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"55m"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">ignore_warnings</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>By leveraging existing configurations and converting them for our Nomad setup, we were able to efficiently deploy the Qwen 3.8 model on our NVIDIA DGX Spark device. This approach not only saved time but also ensured compatibility and optimal performance.</p>]]></content:encoded>
            <category>Devops</category>
            <category>Nomad</category>
            <category>AI</category>
            <category>Nvidia</category>
        </item>
        <item>
            <title><![CDATA[The WebMCP Challenge: Wishlist Concierge]]></title>
            <link>https://blog.bitexpert.de/blog/webmcp_challenge_sylius_wishlist_concierge</link>
            <guid>https://blog.bitexpert.de/blog/webmcp_challenge_sylius_wishlist_concierge</guid>
            <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover how I leveraged the OpenAI WebMCP Challenge to create an innovative Sylius plugin, streamlining e-commerce integrations with AI-powered wishlist management, and explore the technical details behind this cutting-edge solution.]]></description>
            <content:encoded><![CDATA[<p>Last weekend I learned about the <a href="https://openai.com/webmcp-challenge/" target="_blank" rel="noopener noreferrer" class="">OpenAI WebMCP Challenge</a>. A hackathon organized by OpenAI and <a href="https://webmcp.devpost.com/" target="_blank" rel="noopener noreferrer" class="">DevPost</a> to explore what’s possible with <a href="https://webmachinelearning.github.io/webmcp/" target="_blank" rel="noopener noreferrer" class="">WebMCP</a>.</p>
<!-- -->
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-catch">The catch?<a href="https://blog.bitexpert.de/blog/webmcp_challenge_sylius_wishlist_concierge#the-catch" class="hash-link" aria-label="Direct link to The catch?" title="Direct link to The catch?" translate="no">​</a></h3>
<p>Just 10 days of time. When I learned about the hackathon last Sunday, I only had 4 days left to come up with a project idea and the implementation.
Still, I decided to take the challenge.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-webmcp">What is WebMCP?<a href="https://blog.bitexpert.de/blog/webmcp_challenge_sylius_wishlist_concierge#what-is-webmcp" class="hash-link" aria-label="Direct link to What is WebMCP?" title="Direct link to What is WebMCP?" translate="no">​</a></h4>
<p>WebMCP enables web applications to provide JavaScript-based tools to AI agents, allowing them to easily interact with the web application.
Without WebMCP, AI agents have to parse HTML code to understand site structure and functionality.
WebMCP simplifies this process by specifying callable functions and their arguments, enabling faster interaction with web applications.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-plan">The plan?<a href="https://blog.bitexpert.de/blog/webmcp_challenge_sylius_wishlist_concierge#the-plan" class="hash-link" aria-label="Direct link to The plan?" title="Direct link to The plan?" translate="no">​</a></h3>
<p>Being an e-commerce enthusiast, I wanted to create some kind of e-commerce integration. However, not the usual boring "let's add a product to a cart" feature.
And being sold on the <a href="https://www.sylius.com/" target="_blank" rel="noopener noreferrer" class="">Sylius platform</a>, I knew I wanted to build a Sylius plugin.</p>
<p>The first thing I did was to brainstorm with my <a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer" class="">AI coding agent</a> about what to build. We toyed around with several ideas, but one stood out:
<img decoding="async" loading="lazy" alt="The Wishlist Concierge" src="https://blog.bitexpert.de/assets/images/wishlist_concierge-0c75723fc29724af7369a15dc07427f9.png" width="800" height="314" class="img_ev3q"></p>
<p>The <a href="https://github.com/bitExpert/sylius-wishlist-concierge" target="_blank" rel="noopener noreferrer" class="">Wishlist Concierge</a> is an agent + human co-curation themed, budget-aware wishlist solution for Sylius.
Instead of clicking 30 filters, customers can tell their agents “birthday for my nephew” and the AI agent will search taxonomies, build a wishlist, optimize for budget, and move the best fit to cart.</p>
<p>The initial prototype was quickly built.
However, running the first tests was challenging since this is still an experimental feature hidden behind a feature flag in the Chrome browser.
At first, I tried using <a href="https://github.com/microsoft/playwright-cli" target="_blank" rel="noopener noreferrer" class="">playwright-cli</a> in combination with the Playwright skill, but that did not properly work for some reason.
I then moved on to install the ChatGPT desktop application, and that made testing quite smooth.</p>
<p>In the end, the Sylius plugin exposes about 12 WebMCP tools to the AI agent, from product search to creating wishlists, adding and removing products from a wishlist, and converting a wishlist into a cart.</p>
<p>I managed to sneak in a few nice features, e.g. the plugin is using a custom <code>#[ModelContextTool]</code> attribute to annotate controller methods. Via a separate endpoint, the data is collected, and the Javascript frontend registers all tool calls with the WebMCP API:</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name-definition class-name">WishlistController</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AbstractController</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token attribute delimiter punctuation" style="color:#393A34">#[</span><span class="token attribute attribute-content attribute-class-name class-name">ModelContextTool</span><span class="token attribute attribute-content punctuation" style="color:#393A34">(</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">        </span><span class="token attribute attribute-content attribute-class-name class-name">name</span><span class="token attribute attribute-content punctuation" style="color:#393A34">:</span><span class="token attribute attribute-content"> </span><span class="token attribute attribute-content string single-quoted-string" style="color:#e3116c">'wishlist.create'</span><span class="token attribute attribute-content punctuation" style="color:#393A34">,</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">        </span><span class="token attribute attribute-content attribute-class-name class-name">description</span><span class="token attribute attribute-content punctuation" style="color:#393A34">:</span><span class="token attribute attribute-content"> </span><span class="token attribute attribute-content string single-quoted-string" style="color:#e3116c">'Create a new themed wishlist. The active channel is automatically inferred from the current Sylius context. Theme examples: birthday, gift, summer, casual, formal. Name should be human readable like "Birthday Wishlist — $150".'</span><span class="token attribute attribute-content punctuation" style="color:#393A34">,</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">        </span><span class="token attribute attribute-content attribute-class-name class-name">dtoClass</span><span class="token attribute attribute-content punctuation" style="color:#393A34">:</span><span class="token attribute attribute-content"> </span><span class="token attribute attribute-content attribute-class-name class-name">WishlistCreateRequest</span><span class="token attribute attribute-content operator" style="color:#393A34">::</span><span class="token attribute attribute-content constant" style="color:#36acaa">class</span><span class="token attribute attribute-content punctuation" style="color:#393A34">,</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">        </span><span class="token attribute attribute-content attribute-class-name class-name">emitsEvents</span><span class="token attribute attribute-content punctuation" style="color:#393A34">:</span><span class="token attribute attribute-content"> </span><span class="token attribute attribute-content punctuation" style="color:#393A34">[</span><span class="token attribute attribute-content string single-quoted-string" style="color:#e3116c">'webmcp:wishlist-created'</span><span class="token attribute attribute-content punctuation" style="color:#393A34">]</span><span class="token attribute attribute-content punctuation" style="color:#393A34">,</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">        </span><span class="token attribute attribute-content attribute-class-name class-name">routeName</span><span class="token attribute attribute-content punctuation" style="color:#393A34">:</span><span class="token attribute attribute-content"> </span><span class="token attribute attribute-content string single-quoted-string" style="color:#e3116c">'bitexpert_concierge_wishlist_create'</span><span class="token attribute attribute-content punctuation" style="color:#393A34">,</span><span class="token attribute attribute-content"></span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attribute-content">    </span><span class="token attribute attribute-content punctuation" style="color:#393A34">)</span><span class="token attribute delimiter punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name type-declaration">Request</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token class-name return-type">JsonResponse</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Participating in the OpenAI WebMCP Challenge was a valuable experience, providing good insights into the potential of WebMCP and AI agents in e-commerce transactions.</p>
<p>The Wishlist Concierge plugin has sparked ideas for future projects, potentially leading to a conference talk. Stay tuned for more!</p>]]></content:encoded>
            <category>PHP</category>
            <category>Sylius</category>
            <category>AI</category>
            <category>OpenCode</category>
        </item>
        <item>
            <title><![CDATA[Runtime Doctrine ORM analysis with Doctrine Doctor]]></title>
            <link>https://blog.bitexpert.de/blog/doctrine_doctor</link>
            <guid>https://blog.bitexpert.de/blog/doctrine_doctor</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to detect and resolve runtime database issues in Symfony applications using Doctrine Doctor, a powerful tool that analyzes Doctrine queries and identifies performance, security, and configuration problems.]]></description>
            <content:encoded><![CDATA[<p>As developers, we're familiar with static analysis tools like <a href="https://blog.bitexpert.de/blog/tags/phpstan" target="_blank" rel="noopener noreferrer" class="">PHPStan</a> for identifying code problems. However, detecting runtime issues, especially those related to databases, requires a different approach.</p>
<!-- -->
<p>This is where <a href="https://github.com/ahmed-bhs/doctrine-doctor" target="_blank" rel="noopener noreferrer" class="">Doctrine Doctor</a> comes into play. It is a Symfony bundle that extends the Symfony Web Profiler bar to analyze database queries in real-time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-doctrine-doctor">What is Doctrine Doctor?<a href="https://blog.bitexpert.de/blog/doctrine_doctor#what-is-doctrine-doctor" class="hash-link" aria-label="Direct link to What is Doctrine Doctor?" title="Direct link to What is Doctrine Doctor?" translate="no">​</a></h3>
<p>Doctrine Doctor analyzes the actual execution context of your Doctrine queries, including parameters, data volume, and query plans, to identify runtime issues such as N+1 queries, missing indexes, and MySQL configuration problems.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="installing-doctrine-doctor">Installing Doctrine Doctor<a href="https://blog.bitexpert.de/blog/doctrine_doctor#installing-doctrine-doctor" class="hash-link" aria-label="Direct link to Installing Doctrine Doctor" title="Direct link to Installing Doctrine Doctor" translate="no">​</a></h3>
<p>To get started with Doctrine Doctor, run the following command in your terminal:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">composer</span><span class="token plain"> require </span><span class="token parameter variable" style="color:#36acaa">--dev</span><span class="token plain"> ahmed-bhs/doctrine-doctor</span><br></div></code></pre></div></div>
<p>After installation, you'll see Doctrine Doctor in action in the Symfony Profiler bar.</p>
<p><img decoding="async" loading="lazy" alt="Doctrine Docker" src="https://blog.bitexpert.de/assets/images/doctrine_doctor_intro-512fa2e27805206f5a7c3696eacc9dd6.png" width="800" height="345" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="features-and-capabilities">Features and Capabilities<a href="https://blog.bitexpert.de/blog/doctrine_doctor#features-and-capabilities" class="hash-link" aria-label="Direct link to Features and Capabilities" title="Direct link to Features and Capabilities" translate="no">​</a></h3>
<p>Doctrine Doctor boasts over 90 specialized analyzers for performance, security, integrity, and configuration issues. It can detect issues like:</p>
<ol>
<li class="">Initialization problems, such as collections not being properly initialized</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Collection init issue" src="https://blog.bitexpert.de/assets/images/doctrine_doctor_init_problem-3817706dc3cbc14b5b1ab671530cd635.png" width="800" height="472" class="img_ev3q"></p>
<ol start="2">
<li class="">Configuration issues, like a property storing money information using incorrect decimal places</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Money decimal places configuration" src="https://blog.bitexpert.de/assets/images/doctrine_doctor_money-664bc414285fb93339b6417668163817.png" width="800" height="156" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-mate--mcp-integration">AI Mate / MCP Integration<a href="https://blog.bitexpert.de/blog/doctrine_doctor#ai-mate--mcp-integration" class="hash-link" aria-label="Direct link to AI Mate / MCP Integration" title="Direct link to AI Mate / MCP Integration" translate="no">​</a></h3>
<p>For automated issue detection and fixing, Doctrine Doctor offers an optional AI Mate / MCP integration. To enable this feature, update your <code>mate/extensions.php</code> config file as follows:</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string single-quoted-string" style="color:#e3116c">'ahmed-bhs/doctrine-doctor'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string single-quoted-string" style="color:#e3116c">'enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token constant boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string single-quoted-string" style="color:#e3116c">'symfony/ai-symfony-mate-extension'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string single-quoted-string" style="color:#e3116c">'enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token constant boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>By following the steps outlined above, you can improve the performance and reliability of your Symfony applications.
To further enhance your skills, I recommend checking out the official <a href="https://github.com/ahmed-bhs/doctrine-doctor" target="_blank" rel="noopener noreferrer" class="">Doctrine Doctor documentation</a> and experimenting with its features in your own projects.</p>]]></content:encoded>
            <category>PHP</category>
            <category>Symfony</category>
            <category>Sylius</category>
        </item>
        <item>
            <title><![CDATA[Improving Cert Handling in Nomad]]></title>
            <link>https://blog.bitexpert.de/blog/nomad_improved_cert_handling</link>
            <guid>https://blog.bitexpert.de/blog/nomad_improved_cert_handling</guid>
            <pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to streamline certificate management in Nomad and Vault by leveraging a template-driven approach that automates certificate updates and reduces deployment errors.]]></description>
            <content:encoded><![CDATA[<p>When we first started using <a href="https://www.nomadproject.io/" target="_blank" rel="noopener noreferrer" class="">Nomad</a> about 7 years ago, we quickly realized the need for <a href="https://www.vaultproject.io/" target="_blank" rel="noopener noreferrer" class="">Vault</a> to handle secrets and certificates. Initially, we queried certificates from Vault during Docker image build time using a curl command.</p>
<!-- -->
<p>The initial approach involved querying the certificate during Docker build time from Vault like this:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token assign-left variable" style="color:#36acaa">CERT</span><span class="token operator" style="color:#393A34">=</span><span class="token variable" style="color:#36acaa">$(</span><span class="token variable function" style="color:#d73a49">curl</span><span class="token variable" style="color:#36acaa"> </span><span class="token variable parameter variable" style="color:#36acaa">-s</span><span class="token variable" style="color:#36acaa"> </span><span class="token variable parameter variable" style="color:#36acaa">-H</span><span class="token variable" style="color:#36acaa"> </span><span class="token variable string" style="color:#e3116c">"X-Vault-Token:</span><span class="token variable string variable" style="color:#36acaa">$VAULT_TOKEN</span><span class="token variable string" style="color:#e3116c">"</span><span class="token variable" style="color:#36acaa"> </span><span class="token variable parameter variable" style="color:#36acaa">-XGET</span><span class="token variable" style="color:#36acaa"> $VAULT_ADDR/v1/nomad/data/job.prod/certs </span><span class="token variable operator" style="color:#393A34">|</span><span class="token variable" style="color:#36acaa"> jq </span><span class="token variable parameter variable" style="color:#36acaa">-r</span><span class="token variable" style="color:#36acaa"> .data.data.CERTIFICATE</span><span class="token variable" style="color:#36acaa">)</span><br></div></code></pre></div></div>
<p>However, this method had a significant downside: upon certificate renewal, we had to update all our Docker images and redeploy them. Over time, as we added this to more Docker images, we occasionally forgot to update an image.</p>
<p>To address this issue, we decided to change our procedure. Now, Nomad retrieves certificates from Vault, and the Docker entrypoint script processes the data if needed. Although this slightly increases startup time, the benefits outweigh the costs.
As a bonus, whenever we change the certificate in Vault, the Nomad jobs are notified and re-rendered with the updated certificate.</p>
<p>In our Nomad job file, we populate a <a href="https://developer.hashicorp.com/nomad/docs/job-specification/template" target="_blank" rel="noopener noreferrer" class="">template block</a> to fetch the certificate from Vault:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">template</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">data</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token heredoc string" style="color:#e3116c">&lt;&lt;EOH</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">{{ with secret "job.prod/certs" }}</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">{{- .Data.data.CERTIFICATE -}}</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">{{ end }}</span><br></div><div class="token-line" style="color:#393A34"><span class="token heredoc string" style="color:#e3116c">EOH</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">destination</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"secrets/cert.pem"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><br></div></code></pre></div></div>
<p>Then, in the Docker configuration, we mount the <code>/secrets</code> directory to the container and read the certificate from there:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">config</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">image</span><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"nexus.loc/app:1.0.1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">command</span><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/usr/local/bin/entrypoint.sh"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">volumes</span><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"secrets:/secrets"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>By leveraging Nomad and Vault, we've streamlined our certificate management process, eliminating the need for manual updates upon renewal.
This approach has improved our deployment efficiency and reduced the likelihood of errors.</p>]]></content:encoded>
            <category>Devops</category>
            <category>Nomad</category>
            <category>Vault</category>
        </item>
        <item>
            <title><![CDATA[Odoo development with DDEV]]></title>
            <link>https://blog.bitexpert.de/blog/odoo_development_with_ddev</link>
            <guid>https://blog.bitexpert.de/blog/odoo_development_with_ddev</guid>
            <pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to seamlessly integrate Odoo with DDEV, overcoming compatibility hurdles and streamlining your development workflow with this step-by-step technical guide.]]></description>
            <content:encoded><![CDATA[<p>I recently had to develop a tiny <a href="https://www.odoo.com/" target="_blank" rel="noopener noreferrer" class="">Odoo</a> module and wanted to use <a href="https://www.ddev.com/" target="_blank" rel="noopener noreferrer" class="">DDEV</a> for the task. Here's how I set up Odoo with DDEV.</p>
<!-- -->
<p>Thanks to DDEV's flexibility, completing this task was relatively straightforward. Let me walk you through the process.</p>
<p>First, we need to install Odoo in the DDEV Docker image. This is achieved by adding a file named <code>.ddev/web-build/Dockerfile.odoo</code>, which contains additional Dockerfile commands that DDEV will append to its own Dockerfile.
These commands include installing extra packages, as well as installing Odoo from its official Debian repository.</p>
<p>However, I encountered a problem at this stage: The DDEV Docker image is based on Debian Trixie, which is not fully supported by Odoo.
In fact, Odoo depends on the <code>python3-pypdf2</code> package, which is no longer available in Debian Trixie.
Instead, you need to install the <code>python3-pypdf package</code>. This means that Odoo is <a href="https://github.com/odoo/odoo/issues/233667" target="_blank" rel="noopener noreferrer" class="">not installable</a> out of the box on Debian Trixie, unless you install it from source.</p>
<p>After discussing all options with my Coding agent, I decided the best way to fix this is building and installing a fake <code>python3-pypdf2</code> package which resulted in this code:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">mkdir</span><span class="token plain"> /tmp/pypdf2-fake </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name">cd</span><span class="token plain"> /tmp/pypdf2-fake </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name">printf</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'%s\n'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Section: misc'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Priority: optional'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Package: python3-pypdf2'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Version: 9999.0'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Architecture: all'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'Description: Dummy package for python3-pypdf2'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">' This package is provided by Debian python3-pypdf.'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> control </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> equivs-build control </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> dpkg </span><span class="token parameter variable" style="color:#36acaa">-i</span><span class="token plain"> ./python3-pypdf2_9999.0_all.deb </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-rf</span><span class="token plain"> /tmp/pypdf2-fake</span><br></div></code></pre></div></div>
<p>The complete version of the <code>.ddev/web-build/Dockerfile.odoo</code> Dockerfile I added to my project looks like this:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Install required tools needed by Odoo</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token instruction keyword" style="color:#00009f">RUN</span><span class="token instruction"> apt-get update &amp;&amp; apt-get install -y wget gnupg ca-certificates	equivs python3-ldap python3-pypdf python3-venv</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Prepare a fake python3-pypdf2 as Odoo has a hard dependency on this package but it is no longer available in Trixxie</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token instruction keyword" style="color:#00009f">RUN</span><span class="token instruction"> mkdir /tmp/pypdf2-fake </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">    &amp;&amp; cd /tmp/pypdf2-fake </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">    &amp;&amp; printf </span><span class="token instruction string" style="color:#e3116c">'%s\n'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Section: misc'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Priority: optional'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Package: python3-pypdf2'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Version: 9999.0'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Architecture: all'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">'Description: Dummy package for python3-pypdf2'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        </span><span class="token instruction string" style="color:#e3116c">' This package is provided by Debian python3-pypdf.'</span><span class="token instruction"> </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">        &gt; control </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">    &amp;&amp; equivs-build control </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">    &amp;&amp; dpkg -i ./python3-pypdf2_9999.0_all.deb </span><span class="token instruction operator" style="color:#393A34">\</span><span class="token instruction"></span><br></div><div class="token-line" style="color:#393A34"><span class="token instruction">    &amp;&amp; rm -rf /tmp/pypdf2-fake</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Add Odoo repository</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token instruction keyword" style="color:#00009f">RUN</span><span class="token instruction"> wget -O - https://nightly.odoo.com/odoo.key | gpg --dearmor -o /usr/share/keyrings/odoo-archive-keyring.gpg</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token instruction keyword" style="color:#00009f">RUN</span><span class="token instruction"> echo </span><span class="token instruction string" style="color:#e3116c">"deb [signed-by=/usr/share/keyrings/odoo-archive-keyring.gpg] https://nightly.odoo.com/19.0/nightly/deb/ ./"</span><span class="token instruction"> &gt; /etc/apt/sources.list.d/odoo.list</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Install Odoo</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token instruction keyword" style="color:#00009f">RUN</span><span class="token instruction"> apt-get update &amp;&amp; apt-get install -y odoo</span><br></div></code></pre></div></div>
<p>To ensure the <code>/var/lib/odoo</code> directory persists across restarts, I created an additional Docker Compose file <code>.ddev/docker-compose.odoo.yaml</code> that mounts the directory to a Docker volume.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">web</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> ddev</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">odoo</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">/var/lib/odoo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">ddev-odoo</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ddev</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">odoo</span><br></div></code></pre></div></div>
<p>By default, DDEV starts nginx with php-fpm to serve requests. However, we need to run Odoo server instead and expose it to the <a href="https://www.traefik.io/" target="_blank" rel="noopener noreferrer" class="">Traefik</a> proxy that DDEV uses to route requests to the container.
Luckily, DDEV supports generic web servers, as documented in their <a href="https://docs.ddev.com/en/stable/users/quickstart/#generic" target="_blank" rel="noopener noreferrer" class="">official documentation</a>, since early 2025.</p>
<p>All it takes is some additional configuration. To achieve this, I created a file named <code>.ddev/config.odoo.yaml</code> with the following content:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">webserver_type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> generic</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">web_extra_daemons</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"odoo"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"odoo -c /var/www/html/.ddev/odoo/odoo.conf"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">directory</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /var/www/html</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">web_extra_exposed_ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"odoo"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">container_port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8069</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">http_port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">80</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">https_port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><br></div></code></pre></div></div>
<p>The final piece of the puzzle is the Odoo configuration file. To add this, you'll need to create a file named <code>.ddev/odoo/odoo.conf</code>, which will contain the necessary Odoo configuration settings:</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[options]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">addons_path = /var/www/html/odoo_addons</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data_dir = /var/lib/odoo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">db_name = db</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">db_host = db</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">db_port = 5432</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">db_user = db</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">db_password = db</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_password =</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_user =</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_port = 1025</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_server = mailpit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_ssl = False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">email_from = odoo@example.com</span><br></div></code></pre></div></div>
<p>In the root of my project, I created a directory named <code>odoo_addons</code> to store the Odoo modules I want to develop.</p>
<p>To initialize the DDEV setup, run <code>ddev start</code>. This command will build and start the DDEV environment.</p>
<p>Once the setup is up and running, I created the database and Odoo admin user by running:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ddev </span><span class="token builtin class-name">exec</span><span class="token plain"> odoo db init db </span><span class="token parameter variable" style="color:#36acaa">--force</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--username</span><span class="token plain"> admin@example.com </span><span class="token parameter variable" style="color:#36acaa">--password</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'odoo123#'</span><br></div></code></pre></div></div>]]></content:encoded>
            <category>DDEV</category>
            <category>Odoo</category>
        </item>
        <item>
            <title><![CDATA[Give your coding agent a memory]]></title>
            <link>https://blog.bitexpert.de/blog/engram_agent_memory</link>
            <guid>https://blog.bitexpert.de/blog/engram_agent_memory</guid>
            <pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Boost your coding agent's capabilities with Engram, a powerful tool that provides long-term memory and seamless integration, enabling agents to learn and remember over time.]]></description>
            <content:encoded><![CDATA[<p>As I work more intensively with coding agents, I've realized that a long-term memory is important. Without it, agents are limited to knowing only what's happened in the current session.</p>
<!-- -->
<p>Every day, new tools emerge on GitHub to solve this problem. To find the best tool for your needs, you'll need to try them out.
My go-to tool is <a href="https://github.com/Gentleman-Programming/engram" target="_blank" rel="noopener noreferrer" class="">Engram</a>, a Go binary with an SQLite backend that includes FTS5 full-text search capabilities and can be accessed via CLI, HTTP API, an MCP server implementation, and an interactive TUI.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installing-engram">Installing Engram<a href="https://blog.bitexpert.de/blog/engram_agent_memory#installing-engram" class="hash-link" aria-label="Direct link to Installing Engram" title="Direct link to Installing Engram" translate="no">​</a></h2>
<p>To get started with Engram, download the latest release from the <a href="https://github.com/Gentleman-Programming/engram/releases" target="_blank" rel="noopener noreferrer" class="">GitHub releases page</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuring-your-coding-agent">Configuring your Coding agent<a href="https://blog.bitexpert.de/blog/engram_agent_memory#configuring-your-coding-agent" class="hash-link" aria-label="Direct link to Configuring your Coding agent" title="Direct link to Configuring your Coding agent" translate="no">​</a></h2>
<p>Engram supports a variety of different agents, in my case I am using <a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer" class="">OpenCode</a>. For OpenCode the installation procedure is as follows:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">engram setup opencode</span><br></div></code></pre></div></div>
<p>This command adds Engram as an MCP server to your coding agent's config file and installs a plugin for the chosen agent. The plugin ensures the memory is regularly updated.</p>
<p>Usually, the Coding agent plugin will make sure to update the memory in regular intervals. However, if you tell the agent to do so manually, it will also work. I sometimes do this to make sure the latest learnings are documented for future use.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-engram">Why Engram?<a href="https://blog.bitexpert.de/blog/engram_agent_memory#why-engram" class="hash-link" aria-label="Direct link to Why Engram?" title="Direct link to Why Engram?" translate="no">​</a></h2>
<p>I chose Engram for several reasons:</p>
<ul>
<li class="">As a standalone binary with no extra dependencies, Engram is easy to install</li>
<li class="">Engram integrates seamlessly with my coding agent, and the interaction via the agent is smooth</li>
<li class="">The TUI mode is helpful for viewing documented memories</li>
<li class="">Memory Sharing: Memories can be shared via Git with the development team or by running Engram as a self-hosted service</li>
</ul>
<p>By using Engram, I've improved my coding agent's long-term memory, enabling it to learn and remember over time. Give Engram a try to see how it can enhance your coding agent's capabilities.</p>]]></content:encoded>
            <category>AI</category>
            <category>OpenCode</category>
        </item>
        <item>
            <title><![CDATA[DDEV Gally 2.3 Addon Release]]></title>
            <link>https://blog.bitexpert.de/blog/ddev_gally_2_3_0_addon</link>
            <guid>https://blog.bitexpert.de/blog/ddev_gally_2_3_0_addon</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Boost your development environment with our updated Gally DDEV addon, now supporting Gally 2.3 with features like recommender, visitor tracking, and search term suggestions.]]></description>
            <content:encoded><![CDATA[<p>I recently updated our <a class="" href="https://blog.bitexpert.de/blog/ddev_gally_addon">Gally DDEV addon</a> to support the latest <a href="https://github.com/Elastic-Suite/gally/releases/tag/v2.3.0" target="_blank" rel="noopener noreferrer" class="">Gally 2.3</a> release. This new version brings exciting features like a recommender, visitor tracking, and search term suggestions.</p>
<!-- -->
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-new-in-gally-23">What's New in Gally 2.3<a href="https://blog.bitexpert.de/blog/ddev_gally_2_3_0_addon#whats-new-in-gally-23" class="hash-link" aria-label="Direct link to What's New in Gally 2.3" title="Direct link to What's New in Gally 2.3" translate="no">​</a></h3>
<p>Gally 2.3 offers significant updates, including:</p>
<ul>
<li class="">A new recommender feature</li>
<li class="">Visitor tracking</li>
<li class="">Search term suggestions</li>
<li class="">API Platform 4.2 Update and support for PHP 8.4 and later</li>
</ul>
<p>To get the <a href="https://github.com/bitExpert/ddev-gally" target="_blank" rel="noopener noreferrer" class="">latest version</a> of our DDEV addon, compatible with Gally 2.3, you can use the following commands:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ddev add-on get bitExpert/ddev-gally</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ddev restart</span><br></div></code></pre></div></div>
<p>The first time you run the addon, it will build a Gally Docker image from scratch, and this may take a few minutes to complete.</p>
<p>With these updates, you can now integrate Gally 2.3 into your development environment using our DDEV addon. Enjoy the new features and improved performance!</p>]]></content:encoded>
            <category>PHP</category>
            <category>Symfony</category>
            <category>Gally</category>
            <category>DDEV</category>
        </item>
        <item>
            <title><![CDATA[PHPUGMRN 04/26 meetup]]></title>
            <link>https://blog.bitexpert.de/blog/phpugmrn_august_2026</link>
            <guid>https://blog.bitexpert.de/blog/phpugmrn_august_2026</guid>
            <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Join the PHP Usergroup Rhein-Neckar on August 20th as we host Elizabeth Barron, Executive Director of The PHP Foundation, for an exclusive insights into the foundation's mission and upcoming plans.]]></description>
            <content:encoded><![CDATA[<p>On August 20th, the <a href="https://www.meetup.com/phpug-rhein-neckar/events/312782230/" target="_blank" rel="noopener noreferrer" class="">PHP Usergroup Rhein-Neckar metropolitan region</a> is meeting again.</p>
<!-- -->
<p>We're excited to welcome <a href="https://www.linkedin.com/in/elizabethn/" target="_blank" rel="noopener noreferrer" class="">Elizabeth Barron</a> as our guest speaker.
As the Executive Director of <a href="https://thephp.foundation/" target="_blank" rel="noopener noreferrer" class="">The PHP Foundation</a>, Elizabeth will introduce us to the foundation's mission and share insights into upcoming plans.</p>
<p>Interested in attending? Simply sign up on our <a href="https://www.meetup.com/phpug-rhein-neckar/events/312782230/" target="_blank" rel="noopener noreferrer" class="">meetup page</a> to secure your spot!</p>]]></content:encoded>
            <category>PHP</category>
            <category>Usergroup</category>
            <category>MRN</category>
            <category>phpugmrn</category>
        </item>
        <item>
            <title><![CDATA[How we've built the unKonf ticketshop]]></title>
            <link>https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop</link>
            <guid>https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop</guid>
            <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how we successfully migrated our unKonf ticketing platform from Sylius 1 to Sylius 2, overcoming plugin compatibility issues and leveraging AI coding agents to build a custom solution that exactly matches our needs.]]></description>
            <content:encoded><![CDATA[<p>For years, we relied on an external ticketing service for selling <a href="https://shop.unkonf.de/" target="_blank" rel="noopener noreferrer" class="">unKonf tickets</a>.
When the service ceased to exist after COVID-19, we decided to build a custom <a href="https://www.sylius.com/" target="_blank" rel="noopener noreferrer" class="">Sylius</a> platform to handle ticket sales.
Initially, we used Sylius 1 but decided to migrate to Sylius 2 earlier this year due to the end of support for Sylius 1.</p>
<!-- -->
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-did-we-approach-the-migration">How did we approach the migration?<a href="https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop#how-did-we-approach-the-migration" class="hash-link" aria-label="Direct link to How did we approach the migration?" title="Direct link to How did we approach the migration?" translate="no">​</a></h3>
<p>First, we reviewed the plugins used in our Sylius 1 instance and checked their availability for Sylius 2. Some plugins, like <a href="https://github.com/Setono/SyliusTermsPlugin" target="_blank" rel="noopener noreferrer" class="">Setono Terms</a> plugin and <a href="https://github.com/Setono/SyliusRedirectPlugin" target="_blank" rel="noopener noreferrer" class="">Setono Redirects</a> plugin, required updates, which we completed and submitted as PRs on GitHub.</p>
<p>The <a href="https://github.com/Brille24/SyliusCustomOptionsPlugin" target="_blank" rel="noopener noreferrer" class="">CustomOptionsPlugin</a> from Brille24 posed a significant challenge, as it was a key requirement for our ticket sales flow but lacked a Sylius 2 compatible version.
In the end, we decided to build our own simplified version, leveraging AI coding agents to mimic the Sylius attribute system. This approach allowed us to learn how to work with coding agents in a real-world project as well as build a solution that exactly matches our limited requirements.</p>
<p>Transitioning the frontend from Sylius 1 to Sylius 2 was more effort due to changes in HTML and Twig structure.
Even though we've been using the Sylius Bootstrap Theme before, there have been a lot of structural changes that we had to adapt to.
While we could reuse the styling for isolated components like our countdown component, other parts like the main menu bar, the product details page or the checkout had to be rebuilt from scratch.</p>
<p>We started our development with a clean Sylius 2 setup using <a href="https://www.ddev.com/" target="_blank" rel="noopener noreferrer" class="">DDEV</a> which is our default project setup method. First, we installed the necessary plugins and ported the existing backend logic over.
The latter was simple, basically just a copy-and-paste task.
Once we got the backend functionality working, we focused on getting the frontend to look right.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="plugins-we-are-using">Plugins we are using<a href="https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop#plugins-we-are-using" class="hash-link" aria-label="Direct link to Plugins we are using" title="Direct link to Plugins we are using" translate="no">​</a></h3>
<p>We are using the following 3rd party plugins in our ticket shop project:</p>
<ul>
<li class=""><a href="https://github.com/FLUX-SE/SyliusEUVatPlugin" target="_blank" rel="noopener noreferrer" class="">SyliusEUVatPlugin</a> for EU VAT validation and tax handling</li>
<li class=""><a href="https://github.com/monsieurbiz/SyliusHomepagePlugin" target="_blank" rel="noopener noreferrer" class="">SyliusHomepagePlugin</a> and <a href="https://github.com/monsieurbiz/SyliusCmsBlockPlugin" target="_blank" rel="noopener noreferrer" class="">SyliusCmsBlockPlugin</a> for content management</li>
<li class=""><a href="https://github.com/monsieurbiz/SyliusSettingsPlugin" target="_blank" rel="noopener noreferrer" class="">SyliusSettingsPlugin</a> for storing global shop configurations</li>
<li class=""><a href="https://github.com/nelmio/NelmioSecurityBundle" target="_blank" rel="noopener noreferrer" class="">NelmioSecurityBundle</a> to manage Content Security Policy headers</li>
<li class=""><a href="https://github.com/Setono/SyliusRedirectPlugin" target="_blank" rel="noopener noreferrer" class="">SyliusRedirectPlugin</a> for managing redirects</li>
<li class=""><a href="https://github.com/Setono/SyliusTermsPlugin" target="_blank" rel="noopener noreferrer" class="">SyliusTermsPlugin</a> for managing terms and conditions acceptance</li>
<li class=""><a href="https://github.com/Sylius/InvoicingPlugin" target="_blank" rel="noopener noreferrer" class="">InvoicingPlugin</a> for generating invoices</li>
<li class=""><a href="https://github.com/Sylius/MolliePlugin" target="_blank" rel="noopener noreferrer" class="">MolliePlugin</a> for payment handling</li>
<li class="">Our own SyliusOrderRevocationPlugin for compliance with EU Directive 2023/2673 - also known as the "revocation button"</li>
</ul>
<p>Additionally, we've developed a few custom <a href="https://ux.symfony.com/twig-component" target="_blank" rel="noopener noreferrer" class="">Twig components</a> to fetch the main menu structure and sponsors component from our main Sulu website, ensuring data consistency.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h3>
<p>Although we started with a solid foundation - a codebase compatible with Sylius 1.14 and a Bootstrap CSS-based frontend - the migration to Sylius 2 still required significant effort.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="was-it-worth-the-effort">Was it worth the effort?<a href="https://blog.bitexpert.de/blog/how_we_built_unkonf_ticketshop#was-it-worth-the-effort" class="hash-link" aria-label="Direct link to Was it worth the effort?" title="Direct link to Was it worth the effort?" translate="no">​</a></h4>
<p>Overall Sylius 2 has simplified several aspects of our workflow. For instance, we use net pricing internally, as we need to sell tickets without VAT to companies outside of Germany.
In Sylius 1, we worked around this by displaying the gross price from a separate product property and used the net price for the regular calculations.
With Sylius 2, we were able to override the relevant components to display the gross price in the frontend while calculating with the net price in the background.</p>
<p>Also, operating a Sylius store gives us a unique merchant-centric perspective, which proves invaluable in our project work.</p>
<p>By the way, the <a href="https://shop.unkonf.de/" target="_blank" rel="noopener noreferrer" class="">unKonf ticket sale</a> started last week! If you're interested in attending this year's event, act fast - tickets will sell out quickly. Trust my words.</p>]]></content:encoded>
            <category>Sylius</category>
            <category>Symfony</category>
            <category>unKonf</category>
        </item>
        <item>
            <title><![CDATA[Connection Pooling for Onyx AI]]></title>
            <link>https://blog.bitexpert.de/blog/onyx_connection_pooling</link>
            <guid>https://blog.bitexpert.de/blog/onyx_connection_pooling</guid>
            <pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Discover how I optimized Onyx's PostgreSQL connection management to prevent deployment issues, and learn how to tweak its internal settings for a seamless experience.]]></description>
            <content:encoded><![CDATA[<p>For a few months we've been using <a href="https://onyx.app/" target="_blank" rel="noopener noreferrer" class="">Onyx</a> as an internal AI chat platform. Its advanced capabilities like RAG, web search, and deep research make it a valuable tool for us.</p>
<!-- -->
<p>Recently, we ran an issue with Onyx consuming about 50 idle PostgreSQL connections, which caused problems when deploying additional tools in our application cluster.</p>
<p>My first approach was to use <a href="https://www.pgbouncer.org/" target="_blank" rel="noopener noreferrer" class="">pgbouncer</a> to address the issue. However, Onyx itself was unable to handle pgbouncer's connection pooling and failed to start properly.</p>
<p>I opened an <a href="https://github.com/onyx-dot-app/onyx/issues/12505" target="_blank" rel="noopener noreferrer" class="">issue</a> on GitHub, and fortunately, the Onyx team had already resolved the problem.</p>
<p>We now have two options to manage PostgreSQL connections:</p>
<ol>
<li class="">Use Onyx's connection pooling feature: We opted for this approach. You can configure the connection pool and overflow buffer size using the <code>POSTGRES_API_SERVER_POOL_SIZE</code> and <code>POSTGRES_API_SERVER_POOL_OVERFLOW</code> environment variables.</li>
<li class="">Disable connection pooling and use pgbouncer: To disable Onyx's connection pooling feature, set the <code>POSTGRES_USE_NULL_POOL</code> environment variable to true.</li>
</ol>
<p>Adjusting Onyx's internal connection pool settings resolved the issue for us. By choosing one of the two available options, you can effectively manage PostgreSQL connections and avoid potential deployment issues.</p>]]></content:encoded>
            <category>AI</category>
            <category>Onyx</category>
            <category>Postgresql</category>
        </item>
        <item>
            <title><![CDATA[Codebase Memory Management for OpenCode]]></title>
            <link>https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp</link>
            <guid>https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp</guid>
            <pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Boost your OpenCode experience with codebase-memory-mcp to supercharge code search across 158 languages, including infrastructure-as-code indexing.]]></description>
            <content:encoded><![CDATA[<p>Standard code base search in coding agents like <a href="https://www.opencode.ai/" target="_blank" rel="noopener noreferrer" class="">OpenCode</a> can be very limited. To improve my daily workflow, I needed more advanced features like semantic search, call graphs, or an architectural overview.</p>
<!-- -->
<p>I discovered the <a href="https://github.com/DeusData/codebase-memory-mcp" target="_blank" rel="noopener noreferrer" class="">codebase-memory-mcp</a> on GitHub a few months ago. It's a single, static binary that enhances code base search in your coding agent.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-install">How to install?<a href="https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp#how-to-install" class="hash-link" aria-label="Direct link to How to install?" title="Direct link to How to install?" translate="no">​</a></h2>
<p>To get started, download the latest release from <a href="https://github.com/DeusData/codebase-memory-mcp/releases" target="_blank" rel="noopener noreferrer" class="">GitHub</a> and install the binary on your machine.</p>
<p>You can choose between the CLI-only version and the UI variant with 3D graph visualization. For now, I opted for the simple CLI-only version.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-configure-opencode">How to configure OpenCode?<a href="https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp#how-to-configure-opencode" class="hash-link" aria-label="Direct link to How to configure OpenCode?" title="Direct link to How to configure OpenCode?" translate="no">​</a></h2>
<p>To integrate codebase-memory-mcp with OpenCode, add the following MCP (Memory Computation Protocol) configuration to your OpenCode config file:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"$schema"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://opencode.ai/config.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"mcp"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"codebase-memory-mcp"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"local"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"command"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"/usr/local/bin/codebase-memory-mcp"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"enabled"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-use-codebase-memory-mcp">How to use codebase-memory-mcp?<a href="https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp#how-to-use-codebase-memory-mcp" class="hash-link" aria-label="Direct link to How to use codebase-memory-mcp?" title="Direct link to How to use codebase-memory-mcp?" translate="no">​</a></h2>
<p>It starts with indexing a new codebase. Run the command <code>Index this project</code> to start this process. Once the indexing is complete, OpenCode will start searching for code via the codebase-memory-mcp server.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.bitexpert.de/blog/opencode_codebase_memory_mcp#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>I'm impressed with codebase-memory-mcp's simplicity and ease of use. As a single, static binary, it's easy to install and works well with our PHP projects (Symfony/Sylius).</p>
<p>Not using PHP? No worries, it supports about 158 languages, making it a versatile tool for any development team. Moreover, it's not limited to just programming languages; it also handles Infrastructure-as-code indexing, including Dockerfiles and Kubernetes manifests.</p>
<p>Overall, codebase-memory-mcp has become an indispensable tool in my development workflow, and I highly recommend it to any developer or team looking to supercharge their code search and management capabilities.</p>]]></content:encoded>
            <category>AI</category>
            <category>OpenCode</category>
            <category>Symfony</category>
        </item>
        <item>
            <title><![CDATA[How to customize Sylius Grids?]]></title>
            <link>https://blog.bitexpert.de/blog/sylius_grid_customization</link>
            <guid>https://blog.bitexpert.de/blog/sylius_grid_customization</guid>
            <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to customize Sylius grids using PHP event listeners, a flexible and powerful approach to tailoring your grids to your specific needs.]]></description>
            <content:encoded><![CDATA[<p>How can I customize an existing <a href="https://stack.sylius.com/grid/index" target="_blank" rel="noopener noreferrer" class="">Sylius Grid</a>? That's a question that I got asked recently and decided to write about my learnings.</p>
<!-- -->
<p>In many cases, the default grid configuration might not fully meet your requirements. This is where customization comes into play.</p>
<p>While Sylius provides a <a href="https://docs.sylius.com/the-customization-guide/customizing-grids#yaml-based-customizations" target="_blank" rel="noopener noreferrer" class="">YAML configuration</a> approach for customizing grids, I prefer using PHP event listeners for their flexibility. With event listeners, you can use conditions to show or hide fields or actions, making your customizations more dynamic.</p>
<p>Let's create an event listener class to add a new field to the Admin Customer Grid. Here's an example implementation:</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token delimiter important">&lt;?php</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">strict_types</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">namespace</span><span class="token plain"> </span><span class="token package">App</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Grid</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Admin</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Sylius</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Component</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Grid</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Definition</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Field</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Sylius</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Component</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Grid</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Definition</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Filter</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Sylius</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Component</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Grid</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Event</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">GridDefinitionConverterEvent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name-definition class-name">CustomerGridListener</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">editFields</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name type-declaration">GridDefinitionConverterEvent</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword return-type" style="color:#00009f">void</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// get the grid instance</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$grid</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$event</span><span class="token operator" style="color:#393A34">-&gt;</span><span class="token function" style="color:#d73a49">getGrid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// create a new field and add it to the grid</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$field</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name static-context">Field</span><span class="token operator" style="color:#393A34">::</span><span class="token function" style="color:#d73a49">fromNameAndType</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'my_new_field'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string single-quoted-string" style="color:#e3116c">'twig'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$field</span><span class="token operator" style="color:#393A34">-&gt;</span><span class="token function" style="color:#d73a49">setLabel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'app.ui.my_new_field'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$field</span><span class="token operator" style="color:#393A34">-&gt;</span><span class="token function" style="color:#d73a49">setPath</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'.'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$field</span><span class="token operator" style="color:#393A34">-&gt;</span><span class="token function" style="color:#d73a49">setOptions</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string single-quoted-string" style="color:#e3116c">'template'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token string single-quoted-string" style="color:#e3116c">'admin/grid/fields/my_new_field.html.twig'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$grid</span><span class="token operator" style="color:#393A34">-&gt;</span><span class="token function" style="color:#d73a49">addField</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">$field</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>In this example, we create a new field called <code>my_new_field</code> and add it to the grid using the <code>addField()</code> method.</p>
<p>But how does the event listener know which grid to modify? The answer is service tags.</p>
<p>To make Sylius aware of the event listener and bind it to a specific grid, you need to add a service definition to your <code>services.yaml</code> file:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">App\Grid\Admin\CustomerGridListener</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">class</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> App\Grid\Admin\CustomerGridListener</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">tags</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel.event_listener</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">event</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> sylius.grid.admin_customer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">method</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> editFields </span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Here, we define the CustomerGridListener service and tag it with the <code>kernel.event_listener</code> tag. We also specify the grid name <code>sylius.grid.admin_customer</code> and the method to call <code>editFields()</code>.</p>
<p>Customizing Sylius grids using PHP event listeners provides a flexible and powerful way to tailor your grids to your specific needs. By following this approach, you can add custom columns, filters, and actions to your grids, making them more useful and user-friendly.</p>]]></content:encoded>
            <category>PHP</category>
            <category>Sylius</category>
            <category>Symfony</category>
        </item>
        <item>
            <title><![CDATA[Nomad Rescheduling Error]]></title>
            <link>https://blog.bitexpert.de/blog/nomad_rescheduling_error</link>
            <guid>https://blog.bitexpert.de/blog/nomad_rescheduling_error</guid>
            <pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Troubleshooting a mysterious error in Nomad? This post shares a personal experience with a puzzling issue and the simple solution that resolved it.]]></description>
            <content:encoded><![CDATA[<p>While trying to redeploy one of our <a href="https://nomadproject.io/" target="_blank" rel="noopener noreferrer" class="">Nomad</a> jobs last week, I ran into an error that I hadn't seen before.</p>
<!-- -->
<p>This message greeted me after submitting the job:</p>
<p><img decoding="async" loading="lazy" alt="Reschedule error" src="https://blog.bitexpert.de/assets/images/reschedule_error-c54b293243371568865eccaac0c5eb28.png" width="2951" height="301" class="img_ev3q"></p>
<p>I was confused. In the past 8 years of working with Nomad, I had never come across a similar error.
I tried resubmitting the job again, but the same error occurred. I attempted it once more, and the same error persisted.</p>
<p>However, when I resubmitted a different job on the same node, there was no error at all. This only added to my confusion.</p>
<p>I decided to restart the Nomad daemon by running <code>systemctl restart nomad</code> and the error disappeared.</p>
<p>Problem solved. But I am still confused.</p>]]></content:encoded>
            <category>Devops</category>
            <category>linux</category>
            <category>Nomad</category>
        </item>
        <item>
            <title><![CDATA[Forking Repman (our story)]]></title>
            <link>https://blog.bitexpert.de/blog/repman_fork</link>
            <guid>https://blog.bitexpert.de/blog/repman_fork</guid>
            <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We've successfully migrated our private Composer repository from an outdated Repman version to a custom fork supporting PHP 8.5 and Symfony 8.0 by leveraging Rector and AI]]></description>
            <content:encoded><![CDATA[<p>We've been using a private Composer repository since the early days of Composer, starting with <a href="https://github.com/composer/satis" target="_blank" rel="noopener noreferrer" class="">Satis</a>, then migrating to <a href="https://github.com/project-satisfy/satisfy" target="_blank" rel="noopener noreferrer" class="">Satisfy</a>, and finally to <a href="https://github.com/repman-io/repman" target="_blank" rel="noopener noreferrer" class="">Repman</a> for better organization support.</p>
<!-- -->
<p>However, Repman is no longer actively maintained, leaving us with outdated PHP and Symfony versions behind. So what to do?</p>
<p>We could either migrate again or try to maintain a custom fork for our own needs. For now, we decided to go with the latter option. And this is our story.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="upgrading-repman">Upgrading Repman<a href="https://blog.bitexpert.de/blog/repman_fork#upgrading-repman" class="hash-link" aria-label="Direct link to Upgrading Repman" title="Direct link to Upgrading Repman" translate="no">​</a></h4>
<p>To make our Repman fork to support PHP 8.5 and Symfony 8.0, we used <a href="https://github.com/rectorphp/rector" target="_blank" rel="noopener noreferrer" class="">Rector</a> to automatically refactor the code. We did the upgrade in smaller iterations:</p>
<ul>
<li class="">From Symfony 5.4 to 6.0</li>
<li class="">From 6.0 to 6.4</li>
<li class="">From 6.4 to 7.0</li>
<li class="">From 7.0 to 7.4</li>
<li class="">From 7.4 to 8.0 and 8.1</li>
</ul>
<p>The process was relatively straightforward, but in-depth testing revealed issues that required a few manual fixes. After about 2 days, we had a usable application ready.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenges-and-solutions">Challenges and Solutions<a href="https://blog.bitexpert.de/blog/repman_fork#challenges-and-solutions" class="hash-link" aria-label="Direct link to Challenges and Solutions" title="Direct link to Challenges and Solutions" translate="no">​</a></h4>
<p>Rector couldn't fix the unit tests, leaving us with a ton of failing tests. We tried using AI to fix them all at once, but it didn't work as expected.</p>
<p>We then decided to fix each test one by one, removing all failing tests and adding one test case class at a time.
Doing this in a single session in the Coding agent proved to be beneficial since the agent could learn how previous errors have been fixed.
This approach took more time but worked better and helped us learn more about the code base.</p>
<p>We also used the Coding Agent to fix all PHPStan issues, starting with the initial analysis level of 3 and moving to the max level one by one. This worked flawlessly and was quickly done.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="simplification">Simplification<a href="https://blog.bitexpert.de/blog/repman_fork#simplification" class="hash-link" aria-label="Direct link to Simplification" title="Direct link to Simplification" translate="no">​</a></h4>
<p>Once we had all tests working and PHPStan issues fixed, we simplified the code by removing unused features, such as BitBucket integration, telemetry, the user registration flow, and more.</p>
<p>Also, we've introduced the <a href="https://symfony.com/doc/current/frontend/asset_mapper.html" target="_blank" rel="noopener noreferrer" class="">Asset Mapper</a> to manage assets such as CSS and JS files.
This change allows us to control the loading of these assets, rather than relying on direct loading from a CDN.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.bitexpert.de/blog/repman_fork#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h4>
<p>The combination of Rector and the AI Coding agent was a great learning experience for us and made the migration a lot simpler.</p>
<p>Would we recommend others doing the same? Probably not.</p>
<p>In our case, it makes sense since we self-host a lot of tools and managing the fork for us allows a deeper integration of Repman with some other tools we use on a daily basis.
And thanks to <a href="https://github.com/renovatebot/renovate" target="_blank" rel="noopener noreferrer" class="">Renovate Bot</a>, we get automatic updates for the dependencies, making sure we don't fall behind anymore.</p>]]></content:encoded>
            <category>Composer</category>
            <category>Symfony</category>
            <category>Repman</category>
            <category>Renovate</category>
        </item>
        <item>
            <title><![CDATA[Symfony UX Calendar Links]]></title>
            <link>https://blog.bitexpert.de/blog/symfony_ux_calendar_links</link>
            <guid>https://blog.bitexpert.de/blog/symfony_ux_calendar_links</guid>
            <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>As a big Symfony UX fan, I was excited to explore the new components included in the latest Symfony UX release. Previously, I've blogged about <a class="" href="https://blog.bitexpert.de/blog/extend_sylius_twig_component">Twig Components</a> and <a class="" href="https://blog.bitexpert.de/blog/extend_sylius_live_component">Live Components</a>, but there's more to discover.</p>
<!-- -->
<p>The latest Sylius UX release introduces the <a href="https://ux.symfony.com/calendar-link" target="_blank" rel="noopener noreferrer" class="">Calendar Link</a> component, which allows users to add events to their calendar of choice with a single click.</p>
<p>While it may not sound like a revolutionary feature, it's incredibly useful when building a ticket booking site, such as the <a href="http://www.unkonf.de/" target="_blank" rel="noopener noreferrer" class="">unKonf</a> ticket shop, which we're currently upgrading to Sylius 2.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-does-it-work">How does it work?<a href="https://blog.bitexpert.de/blog/symfony_ux_calendar_links#how-does-it-work" class="hash-link" aria-label="Direct link to How does it work?" title="Direct link to How does it work?" translate="no">​</a></h3>
<p>To get started, install the new component using Composer:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">composer</span><span class="token plain"> require symfony/ux-calendar-link</span><br></div></code></pre></div></div>
<p>Next, I decided to create a Twig Component to generate the <code>CalendarEvent</code> object required to render the links:</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token delimiter important">&lt;?php</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">strict_types</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">namespace</span><span class="token plain"> </span><span class="token package">App</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Twig</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Components</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">DateTimeInterface</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Sylius</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">TwigHooks</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Twig</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Component</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">HookableComponentTrait</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Symfony</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">UX</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">CalendarLink</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">CalendarEvent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Symfony</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">UX</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">TwigComponent</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Attribute</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">AsTwigComponent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">Symfony</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">UX</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">TwigComponent</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">Attribute</span><span class="token package punctuation" style="color:#393A34">\</span><span class="token package">ExposeInTemplate</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token attribute delimiter punctuation" style="color:#393A34">#[</span><span class="token attribute attribute-content attribute-class-name class-name">AsTwigComponent</span><span class="token attribute delimiter punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name-definition class-name">CalenderLink</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">use</span><span class="token plain"> </span><span class="token package">HookableComponentTrait</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token attribute delimiter punctuation" style="color:#393A34">#[</span><span class="token attribute attribute-content attribute-class-name class-name">ExposeInTemplate</span><span class="token attribute attribute-content punctuation" style="color:#393A34">(</span><span class="token attribute attribute-content string single-quoted-string" style="color:#e3116c">'event'</span><span class="token attribute attribute-content punctuation" style="color:#393A34">)</span><span class="token attribute delimiter punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">event</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token class-name return-type">CalendarEvent</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token variable" style="color:#36acaa">$timezone</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name class-name-fully-qualified punctuation" style="color:#393A34">\</span><span class="token class-name class-name-fully-qualified">DateTimeZone</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'Europe/Berlin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">CalendarEvent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token argument-name">title</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string single-quoted-string" style="color:#e3116c">'unKonf 2026'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token argument-name">start</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name class-name-fully-qualified punctuation" style="color:#393A34">\</span><span class="token class-name class-name-fully-qualified">DateTimeImmutable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'2026-10-10 09:00'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$timezone</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token argument-name">end</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name class-name-fully-qualified punctuation" style="color:#393A34">\</span><span class="token class-name class-name-fully-qualified">DateTimeImmutable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string single-quoted-string" style="color:#e3116c">'2026-10-10 18:00'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$timezone</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token argument-name">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string single-quoted-string" style="color:#e3116c">'unKonf 2026 - Your barcamp for web and software development!'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token argument-name">location</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string single-quoted-string" style="color:#e3116c">'bitExpert AG, Rudolf-Diesel Str. 40 - 46, 68169 Mannheim, Germany'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Then, in my Twig template, I use the following logic to render the links:</p>
<div class="language-twig codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-twig codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">{% set providers = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    google: {icon: 'logos:google-calendar', class: 'text-calendar-google border-calendar-google hover:bg-calendar-google hover:text-white'},</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    outlook: {icon: 'file-icons:microsoft-outlook', class: 'text-calendar-outlook border-calendar-outlook hover:bg-calendar-outlook hover:text-white'},</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ics: {icon: 'lucide:calendar', class: 'text-calendar-ics border-calendar-ics hover:bg-calendar-ics hover:text-white'},</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">} %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&lt;div class="flex flex-wrap gap-2 justify-center sm:justify-start mt-2"&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    {% for link in ux_calendar_links(event)|filter(link =&gt; providers[link.provider] is defined) %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        {% set p = providers[link.provider] %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        &lt;a href="{{ link.url }}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           target="_blank"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           rel="noopener"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           class="inline-flex items-center gap-2 px-2 py-1 me-3 rounded border text-sm no-underline transition-colors {{ p.class }}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           title="Add to {{ link.label }} calendar"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        &gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            &lt;twig:ux:icon name="{{ p.icon }}" class="size-4"/&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            {{ link.label }}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        &lt;/a&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    {% endfor %}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&lt;/div&gt;</span><br></div></code></pre></div></div>
<p>With these simple steps, you can have the Calendar Link component up and running in little time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.bitexpert.de/blog/symfony_ux_calendar_links#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h3>
<p>The Calendar Link component is a valuable addition to the Symfony UX ecosystem, providing an easy way to integrate calendar functionality into your applications.</p>]]></content:encoded>
            <category>PHP</category>
            <category>Symfony</category>
        </item>
        <item>
            <title><![CDATA[PHPUGMRN 03/26 meetup]]></title>
            <link>https://blog.bitexpert.de/blog/phpugmrn_june_2026</link>
            <guid>https://blog.bitexpert.de/blog/phpugmrn_june_2026</guid>
            <pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Join the PHP User Group Metropolregion Rhein-Neckar on June 25th to learn about Domain-Driven Design (DDD) fundamentals. Improve your software development skills with Sebastian Feldmann's expert insights.]]></description>
            <content:encoded><![CDATA[<p>It's that time again! We're excited to announce the June meetup of the <a href="https://www.meetup.com/phpug-rhein-neckar/events/312782222/" target="_blank" rel="noopener noreferrer" class="">PHP User Group Metropolregion Rhein-Neckar</a>. Join us on the 25th of June for our next meetup.</p>
<!-- -->
<p>We've invited our long-time friend <a href="https://phpc.social/@movetodevnull" target="_blank" rel="noopener noreferrer" class="">Sebastian Feldmann</a> to come over and talk about how to become a <a href="https://martinfowler.com/bliki/DomainDrivenDesign.html" target="_blank" rel="noopener noreferrer" class="">Domain Driven Design (DDD)</a> essentialist.</p>
<p>Domain-Driven Design has been around for a long time. It covers a wide spectrum of useful architectural patterns such as Event Sourcing, CQRS, and Hexagonal Architecture. And because we're technical people, those patterns often become the focus.</p>
<p>But DDD is bigger than its patterns. In his talk, Sebastian revisits the fundamentals and make the case that they matter far more than any particular architecture. If we get those right, our code and applications improve regardless of whether we're using the latest DDD-inspired pattern or none of them at all.</p>
<p>Sounds interesting? Simply sign up on our <a href="https://www.meetup.com/phpug-rhein-neckar/events/312782222/" target="_blank" rel="noopener noreferrer" class="">meetup page</a> to secure your spot!</p>]]></content:encoded>
            <category>PHP</category>
            <category>Usergroup</category>
            <category>MRN</category>
            <category>phpugmrn</category>
        </item>
    </channel>
</rss>