<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged docker</title>
<link>https://coderlegion.com/tag/docker</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to Change the WAF Web Console Listening Port When SafeLine Stays on 9443</title>
<link>https://coderlegion.com/33161/how-to-change-the-waf-web-console-listening-port-when-safeline-stays-on-9443</link>
<description>&lt;p&gt;Changing the WAF web console listening port looks like a one-line edit, but the SafeLine deploy guide only treats one environment key as that port. This troubleshooting note covers one concrete misconfiguration: the operator stored a new port in MGT_PROXY, left MGT_PORT at 9443, and opened a browser URL built from the MGT_PROXY number. The management page did not load. The fix is confirmed when the browser shows the web console on the port named by MGT_PORT, and the administrator reset command still prints a usable login.&lt;/p&gt;
&lt;p&gt;SafeLine is a self-hosted web application firewall. Deployed in front of an application, it works as a reverse proxy: clients reach the WAF first, and the WAF filters and monitors HTTP traffic before a request is forwarded. That protection path is not the management console. The console is the admin page documented in the deploy guide at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;. That guide names the console port with MGT_PORT, described as the web console port to be used. The sample .env sets MGT_PORT=9443, and the Use Web UI section says to open &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. Those two statements are the pair you have to keep aligned.&lt;/p&gt;
&lt;h3 id=&quot;the-misconfiguration&quot;&gt;The misconfiguration&lt;/h3&gt;
&lt;p&gt;Manual setup creates /data/safeline, downloads compose.yaml, and writes .env in that directory. The sample sets MGT_PORT=9443 and MGT_PROXY=0, plus directory, image, database password, subnet, and token values. MGT_PORT is the web console port to be used. MGT_PROXY is the number of console proxy layers. The same guide says MGT_PROXY should only be used when configuring the proxy for the console, and that the default 0 means no proxy is configured. See &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The failed change ignored that split. The operator wanted the console off the sample port, replaced 0 in MGT_PROXY with the desired port, saved the file, and browsed to &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;&lt;/a&gt;: plus that MGT_PROXY value. MGT_PORT was still 9443. The deploy guide never says MGT_PROXY selects the listening port. A non-zero value is a proxy-layer count for a real console proxy setup. Writing a port there does not move the URL under Use Web UI.&lt;/p&gt;
&lt;p&gt;A related mistake shows up after login. The application guide at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt; tells you to open Applications, choose Add Application, and enter Domain, Port, and Upstream. Port there is the port SafeLine will listen on for the original app, such as 80 or 443, with SSL checked for HTTPS apps. That field is not MGT_PORT. Changing it does not rewrite the console address &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-to-compare-before-you-call-it-a-port-change&quot;&gt;What to compare before you call it a port change&lt;/h3&gt;
&lt;p&gt;Use the deploy guide as the checklist and touch only the key it defines as the console port.&lt;/p&gt;
&lt;p&gt;Open .env in the SafeLine directory, shown as /data/safeline in the manual steps. Read MGT_PORT first. That is the web console port to be used, and the published example is 9443. Read MGT_PROXY next. Leave it at 0 unless you are actually configuring proxy layers for the console. Do not store a listen port in MGT_PROXY. Do not use the Applications Port field as the console setting. That field belongs to the protected site.&lt;/p&gt;
&lt;p&gt;The guide applies the environment file when you launch with docker compose up -d from the SafeLine directory. The worked example then opens the console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;, which matches MGT_PORT=9443. If MGT_PORT is still 9443, that is the documented console address, even if another number was typed into MGT_PROXY or into an application form. The sources do not publish a separate post-install migration log, a firewall recipe, or a certificate recipe for an alternate console port. They also do not show a command whose only job is to rebind the console after a later edit. If the page fails on some other number, treat that as a URL mismatch against MGT_PORT, not as proof that the console moved.&lt;/p&gt;
&lt;p&gt;If you are still in the environment-writing step, declare the console port only with MGT_PORT, then launch with the documented docker compose up -d command, and open the console with that same port value. The official success case in the guide is port 9443. Keep the browser pattern identical to Use Web UI, and do not assume an undocumented log line will announce the bind.&lt;/p&gt;
&lt;h3 id=&quot;the-screen-that-confirms-the-fix&quot;&gt;The screen that confirms the fix&lt;/h3&gt;
&lt;p&gt;Restore MGT_PROXY to 0 if you are not configuring a console proxy. Point the browser at the port stored in MGT_PORT. In the documented installation that value is 9443, so the confirming page is &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. The deploy guide says opening this page is how you use the web UI after installation. Seeing that console page is the first confirmation. A connection error or a timeout on any other port is the mismatch, not a successful port move.&lt;/p&gt;
&lt;p&gt;The second confirmation is the account step, not a guessed log path. Run docker exec safeline-mgt resetadmin. When it succeeds, the guide says you must remember the content, then shows the initial username admin, the initial password, and Done. Log in with that username and password on the console page that just loaded. A completed login on the MGT_PORT URL is the screen that proves you are on the web console the install guide configured.&lt;/p&gt;
&lt;p&gt;Do not use a block page as that proof. The application guide says a blocking page means SafeLine defended a test attack against a protected site, and that attack detail is viewed from the web console attack list. A block page means you reached a protected application flow. It does not confirm that MGT_PORT changed, and it does not confirm that MGT_PROXY was a valid place to store a port.&lt;/p&gt;
&lt;p&gt;If resetadmin succeeds but the browser still targets the wrong port, the account step is not the failure. Read MGT_PORT again. The password block only helps after the console page itself is the one from Use Web UI.&lt;/p&gt;
&lt;p&gt;Automatic install does not add a different confirmation. After the documented install command finishes, the deploy guide sends you to Use Web UI, which is the same &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; page. The success text does not print an alternate console port.&lt;/p&gt;
&lt;h3 id=&quot;what-this-check-does-not-claim&quot;&gt;What this check does not claim&lt;/h3&gt;
&lt;p&gt;The home overview at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; describes SafeLine as a self-hosted WAF and reverse proxy with defenses for web attacks, bot abuse defense, HTML and JS code encryption, IP-based rate limiting, and a web access control list. None of those behaviors is a console port switch. Rate limiting, CAPTCHA challenge, authentication challenge, and dynamic protection are traffic controls for protected apps, not substitutes for MGT_PORT.&lt;/p&gt;
&lt;p&gt;This note does not claim that every numeric port is suitable, and it does not add steps the deploy guide does not contain. If the console page does not load on the port written in MGT_PORT, stop and re-check the variable names rather than stacking rewrites that the sources do not describe. After the console loads, protecting an application is still a separate task: add Domain, Port, and Upstream, then resolve the domain to the SafeLine server, as &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt; describes.&lt;/p&gt;
&lt;h4 id=&quot;why-does-the-console-still-open-on-9443-after-i-edited-the-environment-file-&quot;&gt;Why does the console still open on 9443 after I edited the environment file?&lt;/h4&gt;
&lt;p&gt;Because the sample and the Use Web UI section both use 9443, and that number comes from MGT_PORT, not from whichever numeric field you edited. If MGT_PORT is still 9443, the documented console URL is still &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. A change to MGT_PROXY or to an application Port does not replace that page. The variable definitions are in &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;is-the-application-port-the-same-as-the-web-console-port-&quot;&gt;Is the application port the same as the web console port?&lt;/h4&gt;
&lt;p&gt;No. In Add Application, Port is the port SafeLine listens on for the original app, with 80 or 443 given as examples. The web console port is MGT_PORT in .env. Mixing those fields is the misconfiguration above. The application fields are documented at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;what-does-mgt-proxy-change-if-it-is-not-the-listen-port-&quot;&gt;What does MGT_PROXY change if it is not the listen port?&lt;/h4&gt;
&lt;p&gt;The deploy guide defines MGT_PROXY as the number of console proxy layers. It should only be used when configuring the proxy for the console. The default is 0, which means no proxy is configured. It is not documented as the web console listening port.&lt;/p&gt;
&lt;h4 id=&quot;which-screen-confirms-the-console-port-is-the-one-i-intended-&quot;&gt;Which screen confirms the console port is the one I intended?&lt;/h4&gt;
&lt;p&gt;Load &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;&lt;/a&gt;: followed by the MGT_PORT value. For the documented example, that is &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;, and the web console page should appear. Then run docker exec safeline-mgt resetadmin and confirm the initial username admin and the Done line before you log in. That loaded console, not a timeout on another port, is the confirmation described in &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;do-i-need-a-new-administrator-password-after-correcting-the-port-&quot;&gt;Do I need a new administrator password after correcting the port?&lt;/h4&gt;
&lt;p&gt;Only if you run the documented reset. docker exec safeline-mgt resetadmin prints the initial password and Done. Correcting MGT_PROXY back to 0, or opening the URL that already matches MGT_PORT, does not by itself rotate that password. Use the credentials the command prints if you choose to reset, and use them on the console page that matches MGT_PORT.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33161/how-to-change-the-waf-web-console-listening-port-when-safeline-stays-on-9443</guid>
<pubDate>Sat, 31 Oct 2026 02:47:24 +0000</pubDate>
</item>
<item>
<title>Why I Change the WAF Web Console Listening Port Before Turning Protection On</title>
<link>https://coderlegion.com/33160/why-i-change-the-waf-web-console-listening-port-before-turning-protection-on</link>
<description>&lt;p&gt;Before I turn protection on for a live site, I walk a decision checklist. The step I no longer skip is the management listener. Learning how to change the WAF web console listening port belongs in that checklist, because the console is where I will later read blocks, challenges, and attack detail. If I cannot open it on a port I chose, I am not ready to point a domain at the proxy.&lt;/p&gt;
&lt;p&gt;SafeLine is a self-hosted web application firewall. Placed in front of an application, it works as a reverse proxy: clients reach SafeLine first, it filters and monitors HTTP and HTTPS traffic, and it forwards traffic it treats as safe to the original application. The product introduction lists defenses for web attacks, proactive bot abuse defense, HTML and JS code encryption, IP-based rate limiting, and a web access control list. Those controls are useless to me if the console listener was an accident.&lt;/p&gt;
&lt;h3 id=&quot;set-the-console-port-before-the-first-launch&quot;&gt;Set the console port before the first launch&lt;/h3&gt;
&lt;p&gt;The manual deploy page is the place that defines this choice. In the environment file, MGT_PORT is the web console port to be used. The sample sets MGT_PORT=9443, and the same page tells you to open &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; after startup. I treat 9443 as the documented sample, not as a port every host must keep. When that port is already taken, or when my access policy expects the console elsewhere, I change MGT_PORT in .env before I run docker compose up -d. These pages do not describe a console-port switch inside the application dialog, so I do not wait until after DNS and hope one appears.&lt;/p&gt;
&lt;p&gt;The automatic installer is a narrower path. The deploy guide gives one command and then sends you to the web UI, which it shows on port 9443. It does not document a port flag on that command. If I need a non-default console port, I use the manual environment file, where MGT_PORT is explicit, and I decide it before protection is turned on. See the deploy guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MGT_PROXY is a different knob. The guide defines it as the number of console proxy layers, to be used only when configuring a proxy for the console. The default is 0, which means no proxy is configured. Choosing another MGT_PORT does not, by itself, mean I should change MGT_PROXY.&lt;/p&gt;
&lt;h3 id=&quot;keep-that-port-off-the-public-site-path&quot;&gt;Keep that port off the public site path&lt;/h3&gt;
&lt;p&gt;Turning protection on is an application change, not a console login. In Applications, Add Application asks for three facts. Domain is the original app domain, hostname, or IP. Port is the port SafeLine will listen on for that app, such as 80 or 443, with the SSL option checked for HTTPS apps. Upstream is the real address used to forward cleaned traffic. After that, the domain is resolved to the IP of the server where SafeLine runs. Visitors then reach the site through SafeLine. The application steps are documented at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Those fields are easy to mix up with MGT_PORT. They are not the same decision. If I set the application listen port to the console port, administration and the public site fight over one listener. My order is console port first, application listen port second, and DNS last.&lt;/p&gt;
&lt;h3 id=&quot;clear-the-host-secrets-and-login-gates&quot;&gt;Clear the host, secrets, and login gates&lt;/h3&gt;
&lt;p&gt;I also refuse to add an application until the host matches the documented minimums: Linux, x86_64 or arm64, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, 1 CPU core, 1 GB of memory, 5 GB of disk, and a CPU with ssse3. The deploy page lists checks such as uname -m, docker version, docker compose version, free -h, df -h, and lscpu | grep ssse3. I run the ones that apply to this machine instead of assuming a laptop test equals the server.&lt;/p&gt;
&lt;p&gt;ARM is a hard stop in that same page. SafeLine installed on ARM requires a Pro license to work properly, and the Personal Edition is not supported on ARM. If I am on ARM and that license does not apply, I stop before DNS. The LTS choice is equally explicit: RELEASE=-lts selects the LTS line, which the guide says will no longer be maintained starting from 9.1.0-LTS on 14 August 2025. It recommends moving to the latest version, and waiting about a month after a new release if I want caution. I write that choice down before traffic depends on it.&lt;/p&gt;
&lt;p&gt;Secrets come before exposure. The manual steps generate DETECTOR_MGMT_TOKEN and MGT_INTERNAL_TOKEN as 64-character hex values, generate a 16-character alphanumeric DETECTOR_AES_KEY, write them into .env, and set chmod 600 on that file. I do not launch with the sample placeholders still in place. After docker compose up -d, which can take several minutes, I open the console on the port I set. I run docker exec safeline-mgt resetadmin and I keep the initial username and password it prints. The guide shows that initial username as admin. If that page does not load, the listening-port decision is still open, and protection is not ready.&lt;/p&gt;
&lt;h3 id=&quot;name-the-protection-you-are-actually-turning-on&quot;&gt;Name the protection you are actually turning on&lt;/h3&gt;
&lt;p&gt;The introduction lists coverage for SQL injection, XSS, code injection, OS command injection, CRLF injection, LDAP injection, XPath injection, RCE, XXE, SSRF, path traversal, backdoor, bruteforce, HTTP flood, and bot abuse: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The same pages name rate limiting, CAPTCHA challenge, authentication challenge, and dynamic protection. Rate limiting throttles traffic over defined limits, including DoS attempts, bruteforce attempts, and traffic surges. CAPTCHA is documented as a challenge that lets human users through while blocking crawlers and bots. Authentication challenge, when turned on, requires a password or the visitor is blocked. Dynamic protection, when turned on, dynamically encrypts HTML and JS on each visit. I pick the controls this application needs. I do not flip every switch because the container started.&lt;/p&gt;
&lt;p&gt;Before I call the site protected, I use the documented probes: a SQL injection query, an XSS payload, a path traversal sequence, a code injection string, and an XXE doctype, with my own domain in place of the example. A blocking page means that request was defended. I then open the attack list in the console and click detail. An empty list sends me back to domain, listen port, upstream, and DNS. It does not send me to a victory message.&lt;/p&gt;
&lt;p&gt;The checklist I keep is short. Confirm the host, and stop on ARM if the documented license requirement is not met. Set MGT_PORT to the console port I will use, and leave MGT_PROXY at 0 unless the console itself is proxied. Generate the documented secrets and restrict .env. Launch, log in with the resetadmin output, and only then add Domain, Port, and Upstream. Resolve DNS last. Judge the result from the blocking page and the attack detail view.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-change-the-waf-web-console-listening-port-&quot;&gt;How do I change the WAF web console listening port?&lt;/h4&gt;
&lt;p&gt;Set MGT_PORT in the SafeLine .env file used by the manual compose deploy. The deploy guide defines that variable as the web console port to be used and shows 9443 in the sample, with the console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. Put the port you want in MGT_PORT, then start with docker compose up -d. That is a console environment setting. It is not a field in Add Application, and these pages do not describe a later port control in the UI.&lt;/p&gt;
&lt;h4 id=&quot;is-the-web-console-port-the-same-as-the-application-listen-port-&quot;&gt;Is the web console port the same as the application listen port?&lt;/h4&gt;
&lt;p&gt;No. MGT_PORT is only the web console port. In Add Application, Port is what SafeLine listens on for the protected app, such as 80 or 443, and HTTPS apps need the SSL option. Upstream is the real address that receives forwarded traffic. Changing the console port does not change the public site listener.&lt;/p&gt;
&lt;h4 id=&quot;what-should-i-verify-before-i-turn-protection-on-&quot;&gt;What should I verify before I turn protection on?&lt;/h4&gt;
&lt;p&gt;Match the documented host minimums, including Docker 20.10.14 or newer, Docker Compose 2.0.0 or newer, memory, disk, and ssse3. On ARM, confirm the Pro license requirement, because the Personal Edition is not supported there. Generate the documented secrets, open the console on your MGT_PORT, and log in with the account printed by docker exec safeline-mgt resetadmin. Add the application and point DNS at the SafeLine server only after that login works.&lt;/p&gt;
&lt;h4 id=&quot;does-a-different-console-port-require-mgt-proxy-&quot;&gt;Does a different console port require MGT_PROXY?&lt;/h4&gt;
&lt;p&gt;No. MGT_PROXY is the number of console proxy layers. Use it only when you are configuring a proxy for the console. The default of 0 means no proxy is configured. A custom MGT_PORT does not imply a console proxy.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-know-the-protection-decision-worked-&quot;&gt;How do I know the protection decision worked?&lt;/h4&gt;
&lt;p&gt;Send the documented SQL injection, XSS, path traversal, code injection, and XXE test requests to your own domain. Expect the blocking page, then open the console attack list and click detail. If you cannot open the console, fix the listening port and the admin login before you judge the site protected.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33160/why-i-change-the-waf-web-console-listening-port-before-turning-protection-on</guid>
<pubDate>Sat, 31 Oct 2026 02:46:53 +0000</pubDate>
</item>
<item>
<title>How to Reset the WAF Admin Account Inside Docker</title>
<link>https://coderlegion.com/33159/how-to-reset-the-waf-admin-account-inside-docker</link>
<description>&lt;p&gt;Losing the SafeLine console password usually starts with a reasonable but wrong edit. After installation, the web UI is opened at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;, and the deploy guide does not store that login in the Compose environment file. Resetting the administrator account is a container command. The failure mode below is one concrete mix-up: treating POSTGRES_PASSWORD as the console password, then trusting a restart that never printed a new admin credential.&lt;/p&gt;
&lt;h3 id=&quot;what-was-misconfigured&quot;&gt;What was misconfigured&lt;/h3&gt;
&lt;p&gt;A manual install keeps SafeLine configuration and data in a directory you create. The deploy guide at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt; uses /data/safeline as the example. Inside it, .env holds SAFELINE_DIR, IMAGE_TAG, MGT_PORT, POSTGRES_PASSWORD, SUBNET_PREFIX, and several generated secrets. That same guide defines POSTGRES_PASSWORD as the Postgres database initialization password. It is consumed when the database is initialized. It is not the account you type into the management UI.&lt;/p&gt;
&lt;p&gt;The misconfiguration looks like this. The operator cannot log in, opens .env, replaces POSTGRES_PASSWORD, optionally rewrites DETECTOR_MGMT_TOKEN or MGT_INTERNAL_TOKEN, and runs docker compose up -d again. Compose can bring the stack process up. That result does not print an administrator username or a console password. The web account was never that database variable, so the edited string is the wrong secret for the login form.&lt;/p&gt;
&lt;p&gt;File mode is a related dead end. The guide sets .env to mode 600 and restricts resource directories such as resources/mgt and resources/postgres/data. Those permissions protect local secrets and sockets. Changing them does not rotate the console user.&lt;/p&gt;
&lt;h3 id=&quot;reset-the-account-inside-the-management-container&quot;&gt;Reset the account inside the management container&lt;/h3&gt;
&lt;p&gt;Use the procedure in the deploy guide, in order.&lt;/p&gt;
&lt;p&gt;Confirm the host meets the documented dependencies before you blame the password command: Linux, x86_64 or arm64, Docker 20.10.14 or above, and Docker Compose 2.0.0 or above. The guide also lists a minimum of 1 core CPU, 1 GB memory, and 5 GB disk, plus the ssse3 CPU flag. Those checks explain a stack that never became ready. They are not a second password store.&lt;/p&gt;
&lt;p&gt;From the SafeLine directory that contains compose.yaml and .env, start the services with docker compose up -d. The guide warns that this can take several minutes. When it finishes, request the administrator account from inside the management container:&lt;/p&gt;
&lt;p&gt;docker exec safeline-mgt resetadmin&lt;/p&gt;
&lt;p&gt;The container name in that command is safeline-mgt. Do not point docker exec at the Postgres image named in .env, and do not expect the automatic installer shell to reprint the password later. The documented recovery for the administrator account is this exec, described under Get Administrator Account in &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-screen-that-confirms-the-fix-1&quot;&gt;The screen that confirms the fix&lt;/h3&gt;
&lt;p&gt;Success is a terminal screen, not a Compose health line. The deploy guide says that after the command succeeds you must remember the printed content. The confirming text begins with the sentence Please must remember this content. It then prints three [SafeLine] lines: initial username admin, initial password, and Done. The password in the guide is shown masked. On your host, copy the characters that appear in that password field. Asterisks in the documentation are not the credential.&lt;/p&gt;
&lt;p&gt;If that remembered-content screen is absent, the reset is not confirmed. Do not fall back to POSTGRES_PASSWORD, and do not paste a token from .env into the login form. DETECTOR_MGMT_TOKEN and MGT_INTERNAL_TOKEN are internal values written during setup. The guide does not present them as console passwords.&lt;/p&gt;
&lt;p&gt;After the screen is present, open the console with HTTPS on the SafeLine host and the console port. The documented URL is &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. MGT_PORT is defined as the web console port, and the sample .env sets it to 9443. If you changed MGT_PORT before startup, use that port instead. MGT_PROXY is the number of console proxy layers. The sample keeps it at 0 when no console proxy is configured. It does not replace resetadmin.&lt;/p&gt;
&lt;p&gt;The guide then says to enter the password from the previous step, after which you are logged into SafeLine. A successful login with that exact password is the second confirmation. Seeing a block page on a test request only proves that a protected application filtered an attack, as described in &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;. It does not prove that the admin reset ran.&lt;/p&gt;
&lt;h3 id=&quot;what-not-to-reset-by-accident&quot;&gt;What not to reset by accident&lt;/h3&gt;
&lt;p&gt;SafeLine is a self-hosted web application firewall, per &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;. Deployed in front of an application, it is a reverse proxy: clients reach SafeLine first, malicious HTTP and HTTPS traffic is filtered, and safe traffic is forwarded to the upstream. The same overview lists defenses against attacks such as SQL injection, XSS, command injection, path traversal, SSRF, and brute force, plus rate limiting, CAPTCHA challenge, authentication challenge, and dynamic HTML and JS protection. None of that is configured by the admin reset command.&lt;/p&gt;
&lt;p&gt;Application proxy settings are entered after login. The protecting-applications guide tells you to open Applications, add an application, and set domain, listen port, and upstream. Resetting the console user does not document a change to those fields.&lt;/p&gt;
&lt;p&gt;Do not uninstall to recover a password. Uninstall, in the same deploy guide, is docker compose down in the SafeLine directory and then removal of that directory. That deletes local configuration and data. It is a different operation from docker exec safeline-mgt resetadmin.&lt;/p&gt;
&lt;h3 id=&quot;close-the-loop-with-the-original-mistake&quot;&gt;Close the loop with the original mistake&lt;/h3&gt;
&lt;p&gt;Before you leave the host, check four facts against the screen you just saw. The stack was started with docker compose up -d. The reset targeted safeline-mgt. The terminal showed the remember-this-content notice, username admin, a password, and Done. The browser used HTTPS and MGT_PORT, normally 9443, with the password copied from that screen rather than from POSTGRES_PASSWORD.&lt;/p&gt;
&lt;h4 id=&quot;why-does-editing-postgres-password-fail-to-reset-the-console-admin-&quot;&gt;Why does editing POSTGRES_PASSWORD fail to reset the console admin?&lt;/h4&gt;
&lt;p&gt;The deploy guide defines POSTGRES_PASSWORD as the Postgres initialization password in .env. The documented way to obtain the console administrator account is docker exec safeline-mgt resetadmin. A Compose restart can leave the web password unchanged because that variable was never the console credential.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-know-the-container-reset-actually-finished-&quot;&gt;How do I know the container reset actually finished?&lt;/h4&gt;
&lt;p&gt;The confirming screen is the output documented for resetadmin: the notice to remember the content, then [SafeLine] lines for initial username admin, initial password, and Done. No other log in the install guide is described as that credential. If those lines are missing, there is no new admin password to type.&lt;/p&gt;
&lt;h4 id=&quot;which-url-matches-the-password-i-just-reset-&quot;&gt;Which URL matches the password I just reset?&lt;/h4&gt;
&lt;p&gt;Log in at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; unless MGT_PORT in .env was set to another console port. Use HTTPS. The upstream site you are protecting is not the management UI, and a guess on the application listen port will not submit this credential to the console.&lt;/p&gt;
&lt;h4 id=&quot;can-i-reset-the-admin-account-before-docker-compose-up-finishes-&quot;&gt;Can I reset the admin account before docker compose up finishes?&lt;/h4&gt;
&lt;p&gt;The guide places Get Administrator Account after Launch SafeLine. Run docker compose up -d first and allow the start, which may take several minutes. resetadmin is an exec into safeline-mgt, so the management container has to be there for the command to return the success screen.&lt;/p&gt;
&lt;h4 id=&quot;will-this-admin-reset-delete-applications-or-replace-the-database-password-&quot;&gt;Will this admin reset delete applications or replace the database password?&lt;/h4&gt;
&lt;p&gt;The documented command prints an initial username and password for the console. It does not say that applications, upstreams, or POSTGRES_PASSWORD are rewritten. Removing the install directory is the uninstall path, and that path does delete SafeLine data, so keep uninstall and admin reset separate.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33159/how-to-reset-the-waf-admin-account-inside-docker</guid>
<pubDate>Sat, 31 Oct 2026 02:37:39 +0000</pubDate>
</item>
<item>
<title>Why I Run a Checklist Before Turning Protection On</title>
<link>https://coderlegion.com/33158/why-i-run-a-checklist-before-turning-protection-on</link>
<description>&lt;p&gt;I do not turn protection on in the same hour I install a web application firewall. SafeLine sits in front of the origin as a reverse proxy, so a rushed cutover can lock operators out of the console or send visitors to the wrong upstream. The product documentation describes SafeLine as a self-hosted WAF that filters and monitors HTTP traffic between a web application and the Internet, blocking malicious requests and stopping unauthorized data from leaving the app by applying policies that separate malicious traffic from safe traffic (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;). That architecture is why I keep a decision checklist, and why the first operational question is how to reset the WAF admin account inside Docker before any site depends on the proxy.&lt;/p&gt;
&lt;h3 id=&quot;confirm-the-host-before-you-trust-the-console&quot;&gt;Confirm the host before you trust the console&lt;/h3&gt;
&lt;p&gt;The deploy guide sets a floor I will not negotiate: Linux, x86_64 or arm64, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, and at least 1 CPU core, 1 GB of memory, and 5 GB of disk. The CPU must support ssse3. The same page lists the checks I actually run: uname -m, docker version, docker compose version, processor and memory inspection, df -h, and an lscpu filter for ssse3 (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;). If any line fails, protection stays off. On ARM, that guide states that SafeLine requires a Pro license to work properly and that the Personal Edition is not supported on ARM. I treat that as a stop, not a footnote. I also decide the release track before launch. The guide says the LTS line will no longer be maintained starting from 9.1.0-LTS (14 August 2025) and strongly recommends an upgrade to the latest version. I do not put a new site behind an unmaintained image tag just because the compose file still accepts RELEASE=-lts.&lt;/p&gt;
&lt;h3 id=&quot;recover-the-admin-account-while-the-shell-still-works&quot;&gt;Recover the admin account while the shell still works&lt;/h3&gt;
&lt;p&gt;After the stack is up, the deploy guide says to open the web console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; and to get the administrator account with this command:&lt;/p&gt;
&lt;p&gt;docker exec safeline-mgt resetadmin&lt;/p&gt;
&lt;p&gt;A successful run tells you to remember the printed content. It shows an initial username of admin, an initial password, and a done line. I run that command from the Docker host, store the password outside the server, and log in once before I add an application. This is the documented way to obtain the administrator account inside Docker. I do not invent a database edit, a second container name, or a custom reset flag. If the command fails, the container is unhealthy, or I cannot read the password, I am not ready to proxy production traffic. The management UI is how I will later add sites and read the attack list, so a console I cannot enter is a failed checklist, not a minor inconvenience.&lt;/p&gt;
&lt;p&gt;Secrets sit next to that login drill. A manual install keeps configuration in a directory such as /data/safeline, including POSTGRES_PASSWORD and the generated detector and management tokens, and the guide sets .env to mode 600. MGT_PORT is the console port, 9443 in the sample. MGT_PROXY is the number of console proxy layers and should stay at 0 unless a console proxy is actually being configured (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;). Before protection is on, I decide who can reach port 9443 and who can read .env. I do not change MGT_PROXY to paper over a login problem.&lt;/p&gt;
&lt;h3 id=&quot;decide-the-proxy-path-then-decide-which-controls-to-enable&quot;&gt;Decide the proxy path, then decide which controls to enable&lt;/h3&gt;
&lt;p&gt;SafeLine is an nginx-based reverse proxy. It receives traffic for the original site, cleans malicious traffic, and forwards the remainder to the original application (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;). In the Web Admin Console, Add Application asks for Domain, Port, and Upstream. Domain may be a domain name, a hostname, or an IP. Port is what SafeLine will listen on, such as 80 or 443, and HTTPS apps need the SSL option checked. Upstream is the real address used for forwarding. After those values are saved, the domain must resolve to the SafeLine server. If DNS still points at the origin, the site is not protected. If Upstream is wrong, the site is down. I write the three values down and test resolution before I call the change complete.&lt;/p&gt;
&lt;p&gt;The feature choice is a separate decision. Documented capabilities include defenses against SQL injection, XSS, code injection, OS command injection, CRLF injection, LDAP injection, XPath injection, RCE, XXE, SSRF, path traversal, backdoor, brute force, HTTP flood, and bot abuse, plus IP-based rate limiting and a web access control list (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;). The feature list also describes CAPTCHA so human visitors can pass while crawlers and bots are blocked, an authentication challenge that requires a password or else blocks the visitor, and dynamic protection that encrypts HTML and JS on each visit. I do not turn every one of those on during the first cutover. Challenges, dynamic encryption, and tight rate limits change what a legitimate user receives. My default is to prove the proxy and basic attack handling first, and to enable a challenge or dynamic encryption only after I can name the user impact.&lt;/p&gt;
&lt;p&gt;I prove the path with the samples in the application guide, not with live customers. That page shows test requests for SQL injection, XSS, path traversal, code injection, and XXE, and says a blocking page means the attack was defended. I then open the console attack list and a detail view (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;). A normal request should reach the upstream. A documented attack-style request should block or at least show up in that list. If neither happens, I do not declare protection on. I correct the proxy path and repeat the test.&lt;/p&gt;
&lt;p&gt;My go or no-go order is fixed. The host meets the documented CPU, memory, disk, Docker, Compose, and ssse3 requirements, and the ARM license rule is already satisfied if the server is ARM. docker exec safeline-mgt resetadmin has printed an admin password I have stored, and I have logged in on port 9443. Domain, listen port, and upstream are recorded, and DNS points at the SafeLine host. I know which extra controls stay off on day one. One safe request is forwarded and one documented attack-style request is visible as a block or in the attack list. Any failed line means protection stays off.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-reset-the-waf-admin-account-inside-docker-&quot;&gt;How do I reset the WAF admin account inside Docker?&lt;/h4&gt;
&lt;p&gt;On the Docker host, run the command from the deploy guide: docker exec safeline-mgt resetadmin. Success output shows an initial username of admin and an initial password. Store that password and log in at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; before you add an application. These pages do not describe another reset method, so I do not improvise one inside the database container.&lt;/p&gt;
&lt;h4 id=&quot;should-i-turn-protection-on-before-console-login-works-&quot;&gt;Should I turn protection on before console login works?&lt;/h4&gt;
&lt;p&gt;No. Adding an application and reading the attack list both happen in the Web Admin Console. If resetadmin fails or port 9443 is unreachable from your admin network, leave visitor traffic on the origin until the operator path works.&lt;/p&gt;
&lt;h4 id=&quot;what-has-to-be-true-before-an-application-is-actually-protected-&quot;&gt;What has to be true before an application is actually protected?&lt;/h4&gt;
&lt;p&gt;SafeLine must already be installed, the application entry needs Domain, Port, and Upstream, and the domain must resolve to the SafeLine server IP. Protection is that reverse-proxy path plus inspection. A saved form with DNS still aimed at the origin is not protection.&lt;/p&gt;
&lt;h4 id=&quot;which-controls-do-i-leave-off-on-the-first-cutover-&quot;&gt;Which controls do I leave off on the first cutover?&lt;/h4&gt;
&lt;p&gt;I leave CAPTCHA, the authentication challenge, dynamic HTML and JS encryption, and aggressive rate limits off until I have seen normal traffic through the proxy. Those controls are documented, and they change the visitor experience. I validate attack handling with the published test payloads first.&lt;/p&gt;
&lt;h4 id=&quot;does-this-checklist-replace-the-install-guide-&quot;&gt;Does this checklist replace the install guide?&lt;/h4&gt;
&lt;p&gt;No. Automatic install, the compose file, environment variables, permissions, and uninstall steps belong to the official deploy guide. This list is only the set of decisions I finish after install and before real users pass through the proxy. If a recovery or tuning step is not in the supplied install or application docs, I stop rather than invent it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33158/why-i-run-a-checklist-before-turning-protection-on</guid>
<pubDate>Sat, 31 Oct 2026 02:37:07 +0000</pubDate>
</item>
<item>
<title>Building an ATS Resume Scorer API with FastAPI, Celery, and Docker</title>
<link>https://coderlegion.com/33059/building-an-ats-resume-scorer-api-with-fastapi-celery-and-docker</link>
<description>&lt;p&gt;Most resumes are filtered by ATS software before a human ever reads them. This project is a backend API that checks how well a resume matches a job description and suggests how to improve it.&lt;/p&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PDF resume parsing:&lt;/strong&gt; extracts text from uploaded resumes using pdfplumber&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ATS keyword matching:&lt;/strong&gt; compares the resume against a job description and calculates a match score&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart suggestions:&lt;/strong&gt; generates actionable tips to improve the resume for a specific role&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT authentication:&lt;/strong&gt; secured endpoints with role-based access for Job Seekers and Recruiters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch scoring:&lt;/strong&gt; recruiters can score many resumes at once, handled in the background by Celery + Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tech-stack&quot;&gt;Tech stack&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Technology&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend &lt;/td&gt;&lt;td&gt;Python, FastAPI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Auth &lt;/td&gt;&lt;td&gt;JWT (python-jose, passlib)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database &lt;/td&gt;&lt;td&gt;MySQL 8.0, SQLAlchemy ORM, Alembic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Task queue &lt;/td&gt;&lt;td&gt;Celery + Redis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PDF parsing &lt;/td&gt;&lt;td&gt;pdfplumber&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DevOps &lt;/td&gt;&lt;td&gt;Docker, Docker Compose, AWS EC2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD &lt;/td&gt;&lt;td&gt;GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Testing &lt;/td&gt;&lt;td&gt;Pytest&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;design-highlights&quot;&gt;Design highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Background processing:&lt;/strong&gt; batch scoring runs through a Celery queue, so the API stays responsive while many resumes are processed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role-based access:&lt;/strong&gt; Job Seekers and Recruiters get separate permissions on the same API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducible setup:&lt;/strong&gt; the whole stack (API, MySQL, Redis) starts with a single Docker Compose command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated deployment:&lt;/strong&gt; a GitHub Actions pipeline deploys automatically to AWS EC2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database migrations:&lt;/strong&gt; schema changes are managed with Alembic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;source-code&quot;&gt;Source code&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/priyadharshiny13/ats-resume-scorer&quot;&gt;https://github.com/priyadharshiny13/ats-resume-scorer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feedback is welcome. If you were using a tool like this, what would matter more: a more accurate match score or more detailed improvement suggestions?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33059/building-an-ats-resume-scorer-api-with-fastapi-celery-and-docker</guid>
<pubDate>Fri, 30 Oct 2026 07:57:26 +0000</pubDate>
</item>
<item>
<title>How to Install a WAF with the Official Linux Manager Script on a Checked Host</title>
<link>https://coderlegion.com/32928/how-to-install-a-waf-with-the-official-linux-manager-script-on-a-checked-host</link>
<description>&lt;p&gt;A self-hosted web application firewall only helps after it is actually running in front of the origin. SafeLine is a self-hosted WAF that filters and monitors HTTP traffic between a web application and the Internet, and the install guide treats one published command as the recommended automatic path. This article follows that path only: check the Linux host against the documented dependencies, run the official manager script as root, open the web console, and then place an application behind the reverse proxy. It does not invent extra flags, custom ports, or a separate uninstall switch for that script.&lt;/p&gt;
&lt;p&gt;The product introduction describes SafeLine as a reverse-proxy shield. Clients pass through the WAF before they reach the server. It filters, monitors, and blocks malicious HTTP and HTTPS traffic, and it is described as preventing unauthorized data from leaving the application by applying policy. The same page lists typical coverage for SQL injection, XSS, code injection, OS command injection, CRLF injection, LDAP injection, XPath injection, RCE, XXE, SSRF, path traversal, backdoor, brute force, HTTP flood, and bot abuse. Named core capabilities are web-attack defense, proactive bot-abuse defense, HTML and JavaScript code encryption, IP-based rate limiting, and a web access control list. The feature list also describes rate limiting against denial of service, brute force, traffic surges, and other abuse; CAPTCHA challenge; authentication challenge; and dynamic protection that encrypts HTML and JavaScript on each visit. Those controls are configured after installation. They are not arguments to the manager script. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;SafeLine introduction&lt;/a&gt; for that capability list.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;install guide&lt;/a&gt; states the host requirements before either install method. The operating system must be Linux. The instruction architecture must be x86_64 or arm64. Docker must be version 20.10.14 or above, and Docker Compose must be version 2.0.0 or above. The minimum environment is 1 CPU core, 1 GB of memory, and 5 GB of disk. The CPU must support the ssse3 instruction. The same page publishes these confirmation commands:&lt;/p&gt;
&lt;p&gt;uname -m&lt;br&gt;
docker version&lt;br&gt;
docker compose version&lt;br&gt;
cat /proc/cpuinfo | grep &quot;processor&quot;&lt;br&gt;
free -h&lt;br&gt;
df -h&lt;br&gt;
lscpu | grep ssse3&lt;/p&gt;
&lt;p&gt;A missing dependency is a reason to stop. The automatic section does not document switches for architecture, a custom data directory, a custom console port, or an LTS release. Those choices appear in the manual compose section, where you create a SafeLine directory, download compose.yaml, and write .env. If the server is ARM, that manual section states that SafeLine installed on ARM requires a Pro license to work properly and that the Personal Edition is not supported on ARM. Nothing in the automatic command says the manager script waives that constraint, so an ARM host should be treated as a licensing decision before you install.&lt;/p&gt;
&lt;p&gt;When the checks match the guide, the recommended automatic command is the official manager script. It requires root privileges:&lt;/p&gt;
&lt;p&gt;bash -c &quot;$(curl -fsSLk &lt;a rel=&quot;nofollow&quot; href=&quot;https://waf.chaitin.com/release/latest/manager.sh&quot;&gt;https://waf.chaitin.com/release/latest/manager.sh&lt;/a&gt;)&quot; -- --en&lt;/p&gt;
&lt;p&gt;The deploy page says that after this command is executed, installation has succeeded, and you should go directly to the web UI. Open &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; in a browser. The initial administrator account is obtained with a separate documented command:&lt;/p&gt;
&lt;p&gt;docker exec safeline-mgt resetadmin&lt;/p&gt;
&lt;p&gt;On success, the guide shows an initial username of admin, an initial password, and a reminder to keep that output. Log in with that password. Do not assume the script printed a durable account record anywhere else.&lt;/p&gt;
&lt;p&gt;A running console is not the same as a protected site. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;application guide&lt;/a&gt; says SafeLine is a web application firewall developed based on nginx and that it works as an HTTP and HTTPS reverse proxy. It receives traffic for the original website, cleans malicious traffic, and forwards the remaining traffic to the original application. In the web admin console, open Applications and choose Add Application. Enter the domain, hostname, or IP address of the original app; the port SafeLine will listen on, such as 80 or 443; and the upstream address used to forward traffic. For an HTTPS app, check the SSL option. Then resolve the domain you entered to the IP address of the server where SafeLine is located. Until that resolution exists, the path described in the guide is not in place.&lt;/p&gt;
&lt;p&gt;Verification belongs on an application you control. The application guide names SQL injection, XSS, path traversal, code injection, and XXE as sample attack classes, and it says a blocking page means the attack was defended. The web console attack list, including the detail view, is where that guide says to inspect what was recorded. One blocked request does not configure rate limiting, CAPTCHA challenge, authentication challenge, or dynamic protection. Those remain separate features described in the introduction.&lt;/p&gt;
&lt;p&gt;Keep the manual route in reserve rather than mixing it into the script. The install guide documents a directory such as /data/safeline, a requirement of at least 5 GB of disk for that directory, a downloaded compose file, environment variables, secret generation, permission steps, and docker compose up -d. It also documents uninstall as entering the SafeLine directory, running docker compose down, and then removing that directory, only when you are sure you no longer want to use SafeLine. Those are manual steps. They are not published as manager-script options. If you set RELEASE to the LTS value in the manual file, the same page says the LTS version will no longer be maintained starting from 9.1.0-LTS on 14 August 2025, and it strongly recommends that LTS users upgrade to the latest version.&lt;/p&gt;
&lt;p&gt;The supported order is therefore short. Match Linux, architecture, Docker, Docker Compose, ssse3, memory, and disk to the install guide. Run the official manager script with root. Open the console on port 9443 and reset the admin account with the documented container command. Add the application, point the domain at the SafeLine server, and confirm blocks in the console. That is how to install this WAF with the official Linux manager script without adding steps the deploy page does not publish.&lt;/p&gt;
&lt;h4 id=&quot;what-does-the-official-manager-script-require-&quot;&gt;What does the official manager script require?&lt;/h4&gt;
&lt;p&gt;The install guide marks automatic deployment as recommended and says the command requires root privileges. It also requires Linux, x86_64 or arm64, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, a CPU with ssse3, and at least 1 CPU core, 1 GB of memory, and 5 GB of disk. Run the published check commands before the installer.&lt;/p&gt;
&lt;h4 id=&quot;does-a-finished-script-mean-the-site-is-already-protected-&quot;&gt;Does a finished script mean the site is already protected?&lt;/h4&gt;
&lt;p&gt;No. The deploy page says a completed command means installation succeeded and that you should use the web UI. The application guide says protection is the reverse-proxy setup: add the application, set domain, listen port, and upstream, check SSL for HTTPS, and resolve the domain to the SafeLine server IP.&lt;/p&gt;
&lt;h4 id=&quot;where-is-the-first-console-password-documented-&quot;&gt;Where is the first console password documented?&lt;/h4&gt;
&lt;p&gt;Open &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; and run docker exec safeline-mgt resetadmin. The install guide says a successful run shows the initial username admin and an initial password, and it tells you to remember that content before you log in.&lt;/p&gt;
&lt;h4 id=&quot;when-is-the-manual-compose-path-the-documented-alternative-&quot;&gt;When is the manual compose path the documented alternative?&lt;/h4&gt;
&lt;p&gt;Use the manager script for the recommended automatic install. Use the manual section when you need the published compose workflow, including the SafeLine directory, compose.yaml, .env values, generated secrets, and docker compose up -d. On ARM, that section states that a Pro license is required and that the Personal Edition is not supported.&lt;/p&gt;
&lt;h4 id=&quot;which-settings-are-outside-the-automatic-command-&quot;&gt;Which settings are outside the automatic command?&lt;/h4&gt;
&lt;p&gt;The automatic section does not publish a custom console port, an LTS switch, token generation, console proxy layers, or an uninstall flag. ARCH_SUFFIX, RELEASE, MGT_PORT, and related variables are documented in the manual .env instructions. Uninstall is docker compose down in the SafeLine directory, followed by removal of that directory.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32928/how-to-install-a-waf-with-the-official-linux-manager-script-on-a-checked-host</guid>
<pubDate>Thu, 29 Oct 2026 04:37:18 +0000</pubDate>
</item>
<item>
<title>Why I Check the Host Before the Official Linux WAF Manager Script</title>
<link>https://coderlegion.com/32927/why-i-check-the-host-before-the-official-linux-waf-manager-script</link>
<description>&lt;p&gt;A one-line installer is useful only when the host underneath it already matches the product's documented requirements. SafeLine's English deploy guide marks automatic installation as the recommended path, and that path is the official Linux manager script. I still refuse to paste the script into a shell as my first action. The same page lists operating system, architecture, container runtime, memory, disk, and CPU checks before the install command. Those checks are the install, in practice. The script is the commit step.&lt;/p&gt;
&lt;p&gt;SafeLine is a self-hosted web application firewall. Placed in front of a web application, it filters and monitors HTTP traffic between the application and the Internet. The introduction describes it as a reverse proxy: clients pass through the WAF before they reach the server, which limits direct exposure of the origin. Documented attack coverage includes SQL injection, XSS, code injection, OS command injection, CRLF injection, LDAP injection, XPath injection, RCE, XXE, SSRF, path traversal, backdoor, brute force, HTTP flood, and bot abuse. Listed core capabilities are defenses for web attacks, proactive bot-abuse defense, HTML and JS code encryption, IP-based rate limiting, and a web access control list. The feature list also includes CAPTCHA challenge, authentication challenge, and dynamic protection that encrypts HTML and JS on each visit. I cite those capabilities from the product introduction at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; so the install decision stays tied to what the WAF is actually documented to do. Installation does not turn those controls on for a site by itself.&lt;/p&gt;
&lt;p&gt;Before the manager script, I confirm the dependency table in the deploy guide at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;. The operating system must be Linux. The instruction architecture must be x86_64 or arm64. Docker must be version 20.10.14 or above. Docker Compose must be version 2.0.0 or above. The minimum environment is 1 core of CPU, 1 GB of memory, and 5 GB of disk. The CPU must provide the ssse3 instruction. The guide publishes the confirmation commands, and I run them rather than trusting a panel screenshot: uname -m, docker version, docker compose version, cat /proc/cpuinfo | grep &quot;processor&quot;, free -h, df -h, and lscpu | grep ssse3. A host that fails any line is not ready. I do not lower the documented floor to make the script fit a smaller VM.&lt;/p&gt;
&lt;p&gt;The recommended automatic command, which requires root privileges, is bash -c &quot;$(curl -fsSLk &lt;a rel=&quot;nofollow&quot; href=&quot;https://waf.chaitin.com/release/latest/manager.sh&quot;&gt;https://waf.chaitin.com/release/latest/manager.sh&lt;/a&gt;)&quot; -- --en. I copy that command from the deploy guide and I do not append undocumented options. The guide's success criterion is narrow and useful: after the command is executed, the installation is successful, and the next instruction is to use the web UI directly. I treat a clean exit as proof that the instance was installed, not as proof that the origin is protected. If the command fails, I return to the dependency checks and the installation FAQ linked from that page instead of retrying with guessed flags.&lt;/p&gt;
&lt;p&gt;The documented console address is &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. In the manual environment file the management port variable MGT_PORT is set to 9443, which matches that URL. To obtain the administrator account, the guide says to run docker exec safeline-mgt resetadmin. A successful execution prints the initial username admin, an initial password, and a done line. The password belongs to that instance. I record it before closing the session, then log in with that password.&lt;/p&gt;
&lt;p&gt;Login is still only console access. Application protection is a separate procedure, documented at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;. SafeLine is described there as a web application firewall developed based on nginx. It receives traffic for the original website, cleans malicious traffic, and forwards safe traffic upstream. In the web admin console I open Applications and choose Add Application. Domain accepts a domain name, a hostname, or an IP address. Port is the port SafeLine will listen on, such as 80 or 443, and HTTPS applications need the SSL option checked. Upstream is the real address of the original application. After saving, I resolve the entered domain to the IP of the server where SafeLine runs. The same page publishes sample requests for SQL injection, XSS, path traversal, code injection, and XXE. A blocking page means the attack was defended. Details are in the console attack list.&lt;/p&gt;
&lt;p&gt;I keep manual compose as the documented alternative, not as a second opinion from an unrelated guide. Manual deploy installs Docker if needed with the published Docker install command, creates /data/safeline, requires at least 5 GB there, downloads compose.yaml, writes .env, generates a 64-character hex detector management token, a 64-character hex management internal token, and a 16-character alphanumeric detector AES key, restricts .env to mode 600, prepares runtime directories, and starts the stack with docker compose up -d. That is the path I use when I must set SAFELINE_DIR, IMAGE_TAG, MGT_PORT, POSTGRES_PASSWORD, SUBNET_PREFIX, IMAGE_PREFIX, ARCH_SUFFIX, RELEASE, REGION, or MGT_PROXY myself. MGT_PROXY is documented as the number of console proxy layers and should only be used when configuring a proxy for the console. The default is 0, meaning no proxy is configured.&lt;/p&gt;
&lt;p&gt;Two manual settings change the decision even if the manager script is otherwise attractive. On an ARM server the guide says to set ARCH_SUFFIX to -arm and to use the ARM Postgres image. It also states that SafeLine installed on ARM requires a Pro license to work properly, and that the Personal Edition is not supported on ARM. I confirm edition and architecture before I install, because a successful container start is not the same statement as a supported edition. For the LTS line, RELEASE is set to -lts. The guide states that the LTS version will no longer be maintained starting from 9.1.0-LTS (14 August 2025), and that LTS users are strongly recommended to upgrade to the latest version. If there are concerns about a new release, the same note recommends upgrading one month after that release. For a first host I stay on the latest recommended automatic install unless I have a specific reason to select LTS and accept that maintenance status.&lt;/p&gt;
&lt;p&gt;Uninstall is also explicit, and it is destructive. Enter the SafeLine directory, run docker compose down, then remove the directory with rm -rf only when you are sure you no longer want to use SafeLine. I do not use that sequence to reset a host that failed the ssse3 or disk check. Those failures should be fixed, or the install should be abandoned, before any data directory exists.&lt;/p&gt;
&lt;p&gt;The manager script is the right Linux install path when the dependency table already passes, root is available, and I do not need to override the environment variables that only the manual page documents. It is the wrong moment to experiment when architecture, license edition, disk, or Docker version is still unknown.&lt;/p&gt;
&lt;h4 id=&quot;what-does-the-official-manager-script-require-on-linux-&quot;&gt;What does the official manager script require on Linux?&lt;/h4&gt;
&lt;p&gt;The automatic command requires root. The host must already meet the published minimums: Linux, x86_64 or arm64, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, 1 CPU core, 1 GB of memory, 5 GB of disk, and ssse3. I verify those with the commands in the deploy guide before I run the script.&lt;/p&gt;
&lt;h4 id=&quot;is-the-web-console-available-as-soon-as-the-script-finishes-&quot;&gt;Is the web console available as soon as the script finishes?&lt;/h4&gt;
&lt;p&gt;The deploy guide says a completed command means the installation succeeded and that you should open the web UI. The documented console URL is &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;. You still need the administrator password from docker exec safeline-mgt resetadmin before login will succeed.&lt;/p&gt;
&lt;h4 id=&quot;does-installation-alone-proxy-my-existing-site-&quot;&gt;Does installation alone proxy my existing site?&lt;/h4&gt;
&lt;p&gt;No. You must add an application in the console, set domain, listen port, and upstream, and resolve that domain to the SafeLine server. Until that forwarding path exists, the origin is not behind the WAF described in the configuration guide.&lt;/p&gt;
&lt;h4 id=&quot;should-an-arm-host-use-the-same-one-line-install-without-extra-review-&quot;&gt;Should an ARM host use the same one-line install without extra review?&lt;/h4&gt;
&lt;p&gt;Review the ARM notes first. The deploy guide says an ARM install must use the ARM architecture suffix and ARM Postgres image in the manual environment, and that SafeLine on ARM requires a Pro license. The Personal Edition is not supported on ARM.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-remove-a-test-install-without-leaving-the-stack-running-&quot;&gt;How do I remove a test install without leaving the stack running?&lt;/h4&gt;
&lt;p&gt;From the SafeLine directory, run docker compose down. If you are certain you no longer want the product or its data, remove that directory with rm -rf. Do this only after you have exported anything you still need, because the documented removal deletes the directory.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32927/why-i-check-the-host-before-the-official-linux-waf-manager-script</guid>
<pubDate>Thu, 29 Oct 2026 04:36:46 +0000</pubDate>
</item>
<item>
<title>Why I Verify Docker Compose and SSSE3 Before a WAF Install</title>
<link>https://coderlegion.com/32924/why-i-verify-docker-compose-and-ssse3-before-a-waf-install</link>
<description>&lt;p&gt;Before I install a web application firewall, I verify the host against the published dependency table. For SafeLine, that table is not a suggestion buried under a feature list. The deploy guide states Linux, instruction architectures x86_64 and arm64, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, a minimum of 1 CPU core, 1 GB of memory, 5 GB of disk, and CPU ssse3. It also prints the commands that confirm those facts. I care about Docker Compose and SSSE3 in particular because operators often prove only that the docker command exists, then treat the rest as implied. It is not implied.&lt;/p&gt;
&lt;p&gt;SafeLine, as described in the product introduction, is a self-hosted WAF placed in front of a web application. It filters and monitors HTTP traffic and works as a reverse proxy so clients reach the WAF before the origin. That role does not relax the host requirements. A reverse proxy still has to start on a machine that meets the install contract. The introduction page points installation questions to the install guide, and the install guide is where the version and instruction checks live: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I split the preflight into gates that match the command list, not into a single glance that Docker looks fine.&lt;/p&gt;
&lt;p&gt;Architecture comes first. uname -m is the documented way to view the instruction architecture. I am looking for x86_64 or arm64, because those are the two values in the dependency table. A different machine type is a stop, even if Docker happens to be installed.&lt;/p&gt;
&lt;p&gt;The engine and Compose are next, and they are not the same check. docker version is how the guide says to view the Docker version. I need 20.10.14 or above. docker compose version is how the guide says to view the Docker Compose version. I need 2.0.0 or above. I type Compose as a subcommand, because that is the command printed in the guide. A successful engine report does not contain the Compose version. If Compose is missing, or the version is below 2.0.0, I do not continue to the installer and I do not run a compose file just to see. The manual launch step is docker compose up -d, which assumes this dependency is already satisfied.&lt;/p&gt;
&lt;p&gt;SSSE3 is a processor flag, checked with lscpu | grep ssse3. I want the command to print a line that includes ssse3. If it prints nothing, the flag was not found in that output, and I record a failed check. I do not reinterpret silence as probably fine on a modern CPU. The table lists CPU ssse3 on its own row. uname -m does not answer that row. Processor count does not answer it either. cat /proc/cpuinfo | grep &quot;processor&quot; is only the documented view of CPU info for the one-core minimum.&lt;/p&gt;
&lt;p&gt;Memory and disk stay on the same sheet. free -h is the memory check against the 1 GB minimum. df -h is the disk check against the 5 GB minimum. The guide repeats the disk point when it tells you to create a directory: SafeLine writes configuration and data there, and you need at least 5 GB of disk space. In the manual layout that directory is /data/safeline. Checking free space on a different filesystem, then installing onto a small one, does not meet the written requirement.&lt;/p&gt;
&lt;p&gt;ARM needs an extra documented note that is easy to mix up with the ssse3 line. The manual deploy section says SafeLine installed on ARM requires a Pro license to work properly, and that the Personal Edition is not supported on ARM. It also says to set ARCH_SUFFIX=-arm and to use the ARM Postgres image named in the guide. Those are edition and image settings. They are not a substitute for the ssse3 command, and the ssse3 command is not a license check. I write three separate results: architecture, whether lscpu showed ssse3, and whether the edition constraint for ARM applies to this host.&lt;/p&gt;
&lt;p&gt;Only a full pass moves me to deploy. The recommended path in the guide is the automatic installer, which requires root and is the manager.sh command with --en. Manual deploy is the other documented path: install Docker if it is not already present, create /data/safeline, download compose.yaml, write .env, generate the tokens the guide shows, fix permissions, link the socket path, then start the stack. I do not invent a third path when a check fails. The guide's uninstall steps, docker compose down and removal of the SafeLine directory, are for a host you intend to clear, not a way to probe a machine that missed the dependency table.&lt;/p&gt;
&lt;p&gt;Host verification is also not the same as proving the WAF is protecting an application. After a successful start, the deploy guide says to open the web console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt; and to obtain the initial admin account with docker exec safeline-mgt resetadmin. Adding an application is a later configuration step: the console asks for domain, listen port, and upstream. The application document says SafeLine acts as an HTTP or HTTPS reverse proxy, receives traffic for the original website, cleans malicious attack traffic, and forwards the safe and reliable traffic to the original application. Those steps assume the instance is already running on a host that met the install dependencies. I cite that behavior from the official pages rather than from habit: the product overview is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;, the dependency and command list is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;, and the proxy settings are at &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;does-docker-version-replace-docker-compose-version-&quot;&gt;Does docker version replace docker compose version?&lt;/h4&gt;
&lt;p&gt;No. The deploy guide lists Docker 20.10.14 or above and Docker Compose 2.0.0 or above as separate software dependencies, and it tells you to run both docker version and docker compose version. One report does not satisfy the other row.&lt;/p&gt;
&lt;h4 id=&quot;what-if-lscpu-grep-ssse3-prints-nothing-&quot;&gt;What if lscpu | grep ssse3 prints nothing?&lt;/h4&gt;
&lt;p&gt;That command is the published check for the CPU ssse3 instruction. No output means the flag did not appear in lscpu text on this host. I mark the dependency unmet and I do not start installation to discover a workaround. The guide does not document a bypass for a missing ssse3 flag.&lt;/p&gt;
&lt;h4 id=&quot;is-x86-64-or-arm64-enough-by-itself-&quot;&gt;Is x86_64 or arm64 enough by itself?&lt;/h4&gt;
&lt;p&gt;No. Those values satisfy the instruction architecture row only. CPU ssse3 is a separate dependency with its own command. On ARM, the same deploy guide adds a license constraint: a Pro license is required, and the Personal Edition is not supported. I do not collapse architecture, instruction flag, and edition into one result.&lt;/p&gt;
&lt;h4 id=&quot;which-memory-and-disk-numbers-are-in-the-contract-&quot;&gt;Which memory and disk numbers are in the contract?&lt;/h4&gt;
&lt;p&gt;The minimum environment is 1 core CPU, 1 GB memory, and 5 GB disk. free -h and df -h are the commands the guide prints for memory and disk. The directory instructions repeat that at least 5 GB must be available where SafeLine will store configuration and data.&lt;/p&gt;
&lt;h4 id=&quot;should-the-installer-run-before-these-commands-pass-&quot;&gt;Should the installer run before these commands pass?&lt;/h4&gt;
&lt;p&gt;No. The guide places the confirmation commands before the automatic installer and before the manual docker compose up -d step. I use them as entry conditions. A later console login check cannot repair a host that never met Docker Compose 2.0.0 or the ssse3 line.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32924/why-i-verify-docker-compose-and-ssse3-before-a-waf-install</guid>
<pubDate>Thu, 29 Oct 2026 04:26:25 +0000</pubDate>
</item>
<item>
<title>How to Verify Docker Compose and SSSE3 Requirements for SafeLine WAF</title>
<link>https://coderlegion.com/32923/how-to-verify-docker-compose-and-ssse3-requirements-for-safeline-waf</link>
<description>&lt;p&gt;SafeLine is a self-hosted web application firewall that filters and monitors HTTP traffic between a web application and the Internet, and it works as a reverse proxy that cleans malicious traffic before it reaches the origin server (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;). Because it runs as a set of containers managed by Docker Compose, the deployment guide asks you to confirm a short list of prerequisites before installing (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;). The official minimums are a Linux host, x86_64 or arm64 architecture, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, 1 CPU core, 1 GB memory, 5 GB disk, and CPU support for the ssse3 instruction set. The commands below turn that checklist into a repeatable preflight routine, so you can spot a missing dependency before the installer runs.&lt;/p&gt;
&lt;h4 id=&quot;why-does-the-guide-require-docker-compose-2-0-0-or-above-&quot;&gt;Why does the guide require Docker Compose 2.0.0 or above?&lt;/h4&gt;
&lt;p&gt;SafeLine is deployed from a compose.yaml file with &lt;code&gt;docker compose up -d&lt;/code&gt;. The modern &lt;code&gt;docker compose&lt;/code&gt; plugin syntax requires Docker Compose 2.0.0 or above, and the older &lt;code&gt;docker-compose&lt;/code&gt; binary with a hyphen does not satisfy that requirement (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;). Verify both tools in one step:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker version&lt;/code&gt;&lt;br&gt;
&lt;code&gt;docker compose version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Look for the Docker Engine at 20.10.14 or newer and the Compose plugin at 2.0.0 or newer. If &lt;code&gt;docker compose version&lt;/code&gt; returns an error, the Compose plugin is missing or only the legacy binary is installed, and that should be fixed before continuing.&lt;/p&gt;
&lt;h4 id=&quot;what-does-the-ssse3-cpu-requirement-actually-mean-&quot;&gt;What does the ssse3 CPU requirement actually mean?&lt;/h4&gt;
&lt;p&gt;ssse3, or Supplemental Streaming SIMD Extensions 3, is an x86 instruction set extension. The SafeLine deployment prerequisites list CPU ssse3 as a requirement (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;). In practice this means the detection components expect the host CPU to expose that instruction set, so a very old or unusual x86 processor that lacks ssse3 can fail even when Docker and Compose are otherwise fine. Check it directly with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lscpu | grep ssse3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If the command prints a line that lists ssse3 among the supported flags, the CPU is acceptable. No output usually means the flag is absent. On arm64 hosts this x86-specific check does not apply the same way, but the guide still lists arm64 as a supported instruction architecture alongside x86_64 (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id=&quot;which-commands-confirm-the-cpu-memory-and-disk-minimums-&quot;&gt;Which commands confirm the CPU, memory, and disk minimums?&lt;/h4&gt;
&lt;p&gt;The documentation groups the checks into a small command set (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uname -m&lt;/code&gt; reports the instruction architecture.&lt;br&gt;
&lt;code&gt;cat /proc/cpuinfo | grep &quot;processor&quot;&lt;/code&gt; lists the logical processors so you can confirm at least one core.&lt;br&gt;
&lt;code&gt;free -h&lt;/code&gt; shows total and available memory so you can confirm at least 1 GB.&lt;br&gt;
&lt;code&gt;df -h&lt;/code&gt; shows free disk space so you can confirm at least 5 GB in the directory where SafeLine will write its data.&lt;/p&gt;
&lt;p&gt;Run these on the intended host rather than on a jump box or a different machine. The disk check matters because SafeLine writes configuration and data to a directory such as /data/safeline, and the guide asks for at least 5 GB there (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id=&quot;what-is-the-cleanest-pre-install-order-to-follow-&quot;&gt;What is the cleanest pre-install order to follow?&lt;/h4&gt;
&lt;p&gt;Verify in this order: operating system, architecture, ssse3, Docker, Compose, then capacity. That sequence catches the least fixable problems first. If the CPU lacks ssse3, installing newer Docker versions will not help, so it is better to learn that before downloading anything. If Docker is too old, upgrade it before the installer attempts to manage containers. Confirm the host with &lt;code&gt;uname -m&lt;/code&gt;, check &lt;code&gt;lscpu | grep ssse3&lt;/code&gt;, then run &lt;code&gt;docker version&lt;/code&gt; and &lt;code&gt;docker compose version&lt;/code&gt;, and finish with &lt;code&gt;free -h&lt;/code&gt; and &lt;code&gt;df -h&lt;/code&gt;. After all checks pass, the automated installer or the manual compose steps from the guide can proceed, ending with &lt;code&gt;docker compose up -d&lt;/code&gt; and access to the web console on port 9443 by default (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;SafeLine has surpassed 700,000 installations worldwide as of September 30, 2026. SafeLine Pro annual promotional pricing is $650-$880, compared with the regular $1,000 per year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SafeLine website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ly.cyberserval.tech/MqhYdmI&quot;&gt;https://ly.cyberserval.tech/MqhYdmI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pro offer: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32923/how-to-verify-docker-compose-and-ssse3-requirements-for-safeline-waf</guid>
<pubDate>Thu, 29 Oct 2026 04:25:53 +0000</pubDate>
</item>
<item>
<title>What Is an NPU Actually Good For? Testing CPU vs GPU vs NPU for Frigate</title>
<link>https://coderlegion.com/32599/what-is-an-npu-actually-good-for-testing-cpu-vs-gpu-vs-npu-for-frigate</link>
<description>&lt;p&gt;NPUs are suddenly everywhere. Intel, AMD, and Qualcomm are all shipping them in consumer hardware, and the marketing copy makes them sound like a game-changer for AI workloads. But if you run a homelab and care about practical use cases, the real question is: what can an NPU actually do for you today?&lt;/p&gt;
&lt;p&gt;One of the most concrete answers comes from Frigate, the self-hosted NVR that uses object detection to identify people, cars, and animals in your camera feeds. It supports multiple inference backends, which makes it a great real-world benchmark for comparing CPU, GPU, and NPU performance on the same workload.&lt;/p&gt;
&lt;h3 id=&quot;-why-this-matters-for-homelabbers&quot;&gt;💡 Why This Matters for Homelabbers&lt;/h3&gt;
&lt;p&gt;Most homelab AI conversations center on GPUs. A discrete GPU is great for running local LLMs or doing batch inference, but it draws significant power and takes up a PCIe slot. If your goal is always-on, low-latency inference for something like security cameras, that power draw adds up fast.&lt;/p&gt;
&lt;p&gt;An NPU is purpose-built for sustained, efficient inference. It is not trying to compete with a GPU on raw throughput for training or large model inference. It is designed to run smaller, optimized models continuously without hammering your power budget. For Frigate, which is doing object detection on every camera frame all day long, that efficiency story is genuinely relevant.&lt;/p&gt;
&lt;h3 id=&quot;-prerequisites&quot;&gt;🧰 Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A machine with an Intel Core Ultra processor (Meteor Lake, Lunar Lake, Arrow Lake or Panther Lake) for NPU access&lt;/li&gt;
&lt;li&gt;Frigate &lt;strong&gt;0.17 or newer&lt;/strong&gt;, ideally in Docker. NPU support landed in 0.17, so earlier releases will not offer it at all&lt;/li&gt;
&lt;li&gt;OpenVINO detector configured in your Frigate config&lt;/li&gt;
&lt;li&gt;A host kernel with the Intel NPU driver (&lt;code&gt;intel_vpu&lt;/code&gt;) and NPU firmware at least as new as the linux-npu-driver release bundled in your Frigate image. Frigate ships the NPU userspace inside the container, so do not mount host NPU libraries into it. Check the firmware build date with &lt;code&gt;sudo dmesg | grep -i vpu&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Both &lt;code&gt;/dev/dri&lt;/code&gt; and &lt;code&gt;/dev/accel&lt;/code&gt; passed through to the container. The NPU lives at &lt;code&gt;/dev/accel/accel0&lt;/code&gt;, and missing it is the most common reason the NPU never shows up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The MINISFORUM MS-03 used in the source video runs an Intel Core Ultra 9 386H (Panther Lake) with an integrated GPU and a 50 TOPS NPU. One catch: Intel added Panther Lake NPU support in linux-npu-driver 1.28.0, but the official Frigate images bundle older NPU userspace (1.17.0 in 0.17.2 and 1.19.0 in 0.18.0), so Techno Tim ran a rebuilt Frigate 0.17.2 image with a newer Intel stack. On Meteor Lake, Lunar Lake and Arrow Lake, start with the official image. Mini PCs like this are a compelling homelab option because they pack modern silicon into a compact, low-power form factor.&lt;/p&gt;
&lt;h3 id=&quot;-configuring-frigate-with-openvino&quot;&gt;🔧 Configuring Frigate with OpenVINO&lt;/h3&gt;
&lt;p&gt;Frigate supports OpenVINO as a detector type, which is Intel's inference runtime. It can target the CPU, the integrated GPU (via iGPU), or the NPU depending on the device string you pass in your config.&lt;/p&gt;
&lt;p&gt;Step 1: In your &lt;code&gt;frigate.yml&lt;/code&gt;, define a detector block using the OpenVINO type. The &lt;code&gt;device&lt;/code&gt; value controls which processor handles inference. Common options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CPU&lt;/code&gt;: runs on the main processor cores&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GPU&lt;/code&gt;: targets the integrated Intel GPU&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NPU&lt;/code&gt;: targets the dedicated NPU block&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUTO&lt;/code&gt;: lets OpenVINO pick based on availability and load&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;detectors:
  openvino:
    type: openvino
    device: NPU
    model_path: /config/model_cache/yolov9t.xml

# note: the global model block sits at the TOP level, not nested under the detector
model:
  model_type: yolo-generic
  width: 640
  height: 640
  input_tensor: nchw
  input_dtype: float
  labelmap_path: /labelmap/coco-80.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Frigate does not ship a YOLOv9 model. The Frigate docs include a Docker build command that exports YOLOv9 to ONNX in sizes &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;m&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; or &lt;code&gt;e&lt;/code&gt;. Techno Tim's test used YOLOv9-tiny converted to a static 640x640 OpenVINO IR, which is an &lt;code&gt;.xml&lt;/code&gt; file plus a matching &lt;code&gt;.bin&lt;/code&gt;. Put both files in &lt;code&gt;config/model_cache&lt;/code&gt; and point &lt;code&gt;model_path&lt;/code&gt; at the &lt;code&gt;.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Step 2: Pass the device nodes through. This is the step people get wrong: &lt;code&gt;/dev/dri&lt;/code&gt; alone gives you the iGPU but not the NPU. You need &lt;code&gt;/dev/accel&lt;/code&gt; as well, plus the video and render groups.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  frigate:
    image: ghcr.io/blakeblackshear/frigate:0.18.0
    devices:
      - /dev/dri/card0:/dev/dri/card0
      - /dev/dri/renderD128:/dev/dri/renderD128
      - /dev/accel:/dev/accel      # the NPU; without this it is invisible
    group_add:
      - &quot;44&quot;    # video (GIDs vary by distro: check with getent group video)
      - &quot;993&quot;   # render (check with getent group render)
    volumes:
      - /path/to/config:/config
      - /path/to/media:/media/frigate
    ports:
      - &quot;8971:8971&quot;    # authenticated UI and API. Port 5000 is unauthenticated, so do not publish it
      - &quot;8554:8554&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 3: Restart Frigate and check the logs. You should see OpenVINO initialize and report the active device. If the NPU is not detected, the most common cause is a missing driver or an unsupported kernel version. Intel's documentation covers the driver installation steps for each supported OS.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker compose logs -f frigate | grep -i openvino
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-cpu-vs-gpu-vs-npu-what-the-testing-shows&quot;&gt;📊 CPU vs GPU vs NPU: What the Testing Shows&lt;/h3&gt;
&lt;p&gt;Based on the Techno Tim video linked below, running the same Frigate object detection workload across all three backends on the same Intel Core Ultra hardware produced some clear patterns worth understanding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU:&lt;/strong&gt; Works out of the box with no extra configuration. Inference is functional but burns CPU cycles that could be going to other processes. On a machine doing double duty as a NAS or VM host, this matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iGPU:&lt;/strong&gt; Faster inference than CPU and offloads the work from your main cores. A solid middle ground that works well on most modern Intel platforms with proper drivers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NPU:&lt;/strong&gt; The NPU trades raw peak throughput for consistent, power-efficient inference. It is not necessarily faster than the iGPU on a frames-per-second basis, but it can free the iGPU up for other tasks like video transcoding, and it does the work at a lower power cost. For an always-on Frigate instance running 24/7, that matters more than peak speed.&lt;/p&gt;
&lt;p&gt;The practical takeaway is that the NPU is not a replacement for a GPU in general-purpose AI workloads. It is a purpose-fit accelerator for sustained, low-complexity inference, exactly what Frigate does all day long. Frigate's own documentation makes the same split explicit: if you have both, run object detection on the NPU and leave the iGPU free for enrichments like semantic search and face recognition, plus video transcoding.&lt;/p&gt;
&lt;h3 id=&quot;-security-notes&quot;&gt;🛡️ Security Notes&lt;/h3&gt;
&lt;p&gt;Passing hardware device nodes into Docker containers does expand the attack surface slightly. A few things worth keeping in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only expose the specific device nodes Frigate needs, not broad access to all of &lt;code&gt;/dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the Frigate container without unnecessary capabilities&lt;/li&gt;
&lt;li&gt;Keep Frigate and the host OS updated, especially OpenVINO and Intel driver packages&lt;/li&gt;
&lt;li&gt;Put Frigate on a dedicated VLAN or network segment away from your cameras if possible&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-troubleshooting&quot;&gt;🔧 Troubleshooting&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NPU device not found:&lt;/strong&gt; First check that &lt;code&gt;/dev/accel/accel0&lt;/code&gt; exists on the host and that you actually passed &lt;code&gt;/dev/accel&lt;/code&gt; into the container. The NPU firmware is loaded by the host kernel and is not part of the Frigate image, so a kernel without the Intel NPU driver will never expose the device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confirm which device is actually in use:&lt;/strong&gt; At startup Frigate logs &lt;code&gt;Loading OpenVINO model ... on device NPU&lt;/code&gt;, and it does not quietly move object detection from the NPU to the CPU. If the NPU is missing or its firmware is too old, the detector fails to load and the error appears in &lt;code&gt;docker compose logs&lt;/code&gt;. Firmware older than the bundled driver can fail with &lt;code&gt;MAPPED_INFERENCE_VERSION is NOT compatible with the ELF&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High latency on first detection:&lt;/strong&gt; OpenVINO compiles the model for the selected device every time the detector starts, and the first inference includes that compilation. On the NPU, Intel's driver caches the compiled model by default, so later starts are usually quicker as long as that cache survives. Give it a few minutes to warm up before drawing conclusions about performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Model compatibility:&lt;/strong&gt; Not all models work on the NPU. NPU compilation requires static input shapes, which is why the config above pins 640x640 rather than leaving the size dynamic. Stick to models Frigate ships or lists as tested for NPU use.&lt;/p&gt;
&lt;h3 id=&quot;-wrap-up&quot;&gt;✅ Wrap Up&lt;/h3&gt;
&lt;p&gt;NPUs are not hype for every workload, but for always-on inference tasks like Frigate object detection, they are a genuinely useful tool. The key insight is matching the right accelerator to the right job. CPU is flexible, iGPU is fast, and the NPU is efficient for sustained, repetitive inference.&lt;/p&gt;
&lt;p&gt;If you are running a mini PC with a modern Intel Core Ultra chip and you have Frigate deployed, it is worth spending an afternoon switching the OpenVINO device string and watching your system metrics. You might free up meaningful resources without spending anything extra.&lt;/p&gt;
&lt;p&gt;Full credit for the hands-on testing goes to Techno Tim. Watch the original video for the full walkthrough and real metric comparisons: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=8NFV6duMKQA&quot;&gt;What Is an NPU Actually Good For? on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;related-from-the-lab&quot;&gt;Related from the lab&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codewithromi.com/run-ai-workloads-on-the-minisforum-n5-max-strix-halo-nas-with-docker/&quot;&gt;Run AI Workloads on the MinisForum N5 Max (Strix Halo NAS) with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codewithromi.com/local-ai-self-hosting-best-practices-for-your-homelab/&quot;&gt;Local AI Self-Hosting: Best Practices for Your Homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codewithromi.com/self-host-your-photos/&quot;&gt;Self-Host Your Photos with Immich&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://codewithromi.com/what-is-an-npu-actually-good-for-testing-cpu-vs-gpu-vs-npu-for-frigate/&quot;&gt;codewithromi.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32599/what-is-an-npu-actually-good-for-testing-cpu-vs-gpu-vs-npu-for-frigate</guid>
<pubDate>Mon, 26 Oct 2026 23:59:38 +0000</pubDate>
</item>
<item>
<title>How to Run SafeLine WAF in Docker Swarm: A Deployment Checklist</title>
<link>https://coderlegion.com/31918/how-to-run-safeline-waf-in-docker-swarm-a-deployment-checklist</link>
<description>&lt;p&gt;Running SafeLine WAF in Docker Swarm requires careful planning because the supplied SafeLine deployment guide documents Docker Compose, not a native Docker Swarm installation. The safest approach is to treat Swarm as an orchestration decision that needs validation against the official deployment model, rather than assuming that a Compose file can be converted without changes.&lt;/p&gt;
&lt;p&gt;SafeLine is a self hosted web application firewall that works as a reverse proxy between clients and an application. It filters, monitors, and blocks malicious HTTP or HTTPS traffic before forwarding acceptable traffic to the original application. The official introduction describes protection against threats including SQL injection, XSS, code injection, path traversal, SSRF, XXE, brute force attempts, and HTTP floods. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Begin with an environment inventory for every possible Swarm node. The official deployment requirements list Linux, x86_64 or arm64 architecture, Docker 20.10.14 or newer, Docker Compose 2.0.0 or newer, at least one CPU core, 1 GB of memory, 5 GB of disk space, and SSSE3 CPU support. These requirements are written for the documented SafeLine setup, so verify how they map to your Swarm nodes before scheduling workloads. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, decide where persistent SafeLine configuration and data will live. The guide creates /data/safeline and states that SafeLine writes its configuration and data there. In a Swarm design, this raises practical questions about node placement, storage availability, recovery, and whether a task can restart on a node that contains the required data. Resolve those questions before exposing production traffic.&lt;/p&gt;
&lt;p&gt;The documented installation also uses environment values for the SafeLine directory, image tag, management port, PostgreSQL password, internal subnet prefix, image prefix, architecture suffix, release channel, region, and management proxy layers. Review each value and preserve secrets securely. The official guide states that ARM deployments require ARCH_SUFFIX=-arm and that ARM installations require a Pro license, while the Personal Edition is not supported on ARM. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After the platform design is validated, confirm how the documented Compose lifecycle maps to your Swarm operating procedures. The official guide uses docker compose up -d, opens the management console at &lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;safeline-ip&amp;gt;:9443/&quot;&gt;https://&amp;lt;safeline-ip&amp;gt;:9443/&lt;/a&gt;, and retrieves the initial administrator account with docker exec safeline-mgt resetadmin. Do not assume these commands have identical behavior after a Swarm conversion. Test management access, data persistence, restart behavior, logging, and secret handling in a nonproduction environment first.&lt;/p&gt;
&lt;p&gt;Once SafeLine is available, configure protection through its Web Admin Console. The application guide instructs administrators to add an application with its domain, listening port, SSL setting when applicable, and upstream address. DNS should then resolve the application domain to the server where SafeLine is located. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price.&lt;/p&gt;
&lt;h4 id=&quot;does-the-official-guide-provide-a-docker-swarm-command-&quot;&gt;Does the official guide provide a Docker Swarm command?&lt;/h4&gt;
&lt;p&gt;No. The supplied deployment guide documents Docker and Docker Compose. It does not provide a Docker Swarm stack file, Swarm-specific service commands, overlay network instructions, or a supported Swarm conversion procedure. Treat Swarm deployment as an adaptation that requires independent validation.&lt;/p&gt;
&lt;h4 id=&quot;what-should-be-checked-before-scheduling-safeline-on-swarm-nodes-&quot;&gt;What should be checked before scheduling SafeLine on Swarm nodes?&lt;/h4&gt;
&lt;p&gt;Check Linux compatibility, CPU architecture, Docker version, available memory, disk capacity, SSSE3 support, persistent storage, management port access, and secure handling of the PostgreSQL initialization password. These checks come from the documented SafeLine requirements and configuration model.&lt;/p&gt;
&lt;h4 id=&quot;how-does-safeline-receive-application-traffic-&quot;&gt;How does SafeLine receive application traffic?&lt;/h4&gt;
&lt;p&gt;SafeLine acts as an HTTP or HTTPS reverse proxy. In the Web Admin Console, define the application domain, listening port, SSL option when needed, and upstream address. Then point the domain to the SafeLine server so traffic reaches the proxy before the original application. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;how-can-a-swarm-deployment-be-accepted-for-production-&quot;&gt;How can a Swarm deployment be accepted for production?&lt;/h4&gt;
&lt;p&gt;Create a repeatable test that verifies installation, administrator access, application routing, TLS behavior, persistence after task movement, recovery from node failure, and review of attack events. Use the official SafeLine documentation as the reference for product behavior, and document every Swarm-specific change separately.&lt;/p&gt;
&lt;p&gt;Use the official documentation to validate your architecture and invite your operations team to review the storage, networking, and recovery plan before production deployment. Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; Promotion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31918/how-to-run-safeline-waf-in-docker-swarm-a-deployment-checklist</guid>
<pubDate>Wed, 21 Oct 2026 04:27:52 +0000</pubDate>
</item>
<item>
<title>How to Run SafeLine WAF in Docker Swarm Without Skipping the Deployment Checklist</title>
<link>https://coderlegion.com/31917/how-to-run-safeline-waf-in-docker-swarm-without-skipping-the-deployment-checklist</link>
<description>&lt;p&gt;Running a web application firewall in Docker Swarm starts with a deployment decision: determine which parts of the documented SafeLine installation can be carried into your orchestration model, then verify every assumption before exposing an application. The supplied SafeLine installation guide documents Linux, Docker, Docker Compose, and a Compose based launch with &lt;code&gt;docker compose up -d&lt;/code&gt;. It does not document a Docker Swarm stack file or claim Swarm support. Therefore, treat Swarm deployment as an evaluation exercise rather than assuming that the documented Compose configuration is a supported Swarm integration.&lt;/p&gt;
&lt;p&gt;SafeLine is a self hosted web application firewall that operates as a reverse proxy. It receives traffic for an original application, filters malicious requests, and forwards safe traffic to the upstream application. The official application guide describes the required application settings as domain, listening port, optional SSL, and upstream address. After configuration, the application domain should resolve to the server running SafeLine. Sources: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Begin with a compatibility inventory. The official deployment guide lists Linux, x86_64 or arm64 architecture, Docker 20.10.14 or later, Docker Compose 2.0.0 or later, and a minimum environment of 1 CPU core, 1 GB memory, and 5 GB disk. It also calls for SSSE3 CPU support. Check the host architecture, Docker version, Compose version, processor information, memory, and disk before planning a migration. These checks establish whether the documented SafeLine prerequisites are present, but they do not establish that a multi node Swarm deployment is supported. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pay close attention to persistent state. The guide creates &lt;code&gt;/data/safeline&lt;/code&gt;, downloads &lt;code&gt;compose.yaml&lt;/code&gt;, and places configuration and data there. In a Swarm design, you must decide how that directory is made available to the task that runs SafeLine and how it is backed up. The supplied documents do not specify shared storage, node constraints, failover behavior, rolling updates, or Swarm aware backup procedures. Record each of those as an operational requirement that needs validation in your environment.&lt;/p&gt;
&lt;p&gt;The documented environment variables also deserve a review before any translation. They include the SafeLine directory, image tag, management port, PostgreSQL password, internal subnet prefix, image prefix, architecture suffix, release selection, and management proxy layers. The guide says to use &lt;code&gt;ARCH_SUFFIX=-arm&lt;/code&gt; on ARM and explains that ARM installation requires a Pro license, while the Personal Edition is not supported on ARM. It also documents an LTS setting and notes that the LTS line will no longer be maintained starting from 9.1.0-LTS. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A sensible proof of concept should keep one controlled node, a test hostname, a noncritical upstream, and a rollback path. Confirm that the management console is reachable at the configured management port, retrieve the initial administrator account using the documented &lt;code&gt;docker exec safeline-mgt resetadmin&lt;/code&gt; command, and log in through the Web UI. Then add the test application in the Applications page and verify that DNS points to the SafeLine host. Do not label the result production ready until traffic routing, persistent data, certificate handling, node placement, restart behavior, and recovery have all been tested.&lt;/p&gt;
&lt;p&gt;SafeLine documentation describes protections including web attack blocking, rate limiting, CAPTCHA challenges, authentication challenges, and dynamic HTML and JavaScript protection. Validate only the protections you enable and review the attack list in the console after controlled testing. The application guide provides examples for SQL injection, XSS, path traversal, code injection, and XXE testing. Run such tests only against an application you own or are authorized to assess. Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price. Treat those figures as the stated promotional information for this article and verify current availability through the supplied promotion link before making a purchase decision.&lt;/p&gt;
&lt;h4 id=&quot;is-docker-swarm-officially-documented-for-safeline-&quot;&gt;Is Docker Swarm officially documented for SafeLine?&lt;/h4&gt;
&lt;p&gt;The supplied deployment material documents Docker Compose commands and a Compose file. It does not provide a Swarm deployment procedure or confirm Swarm compatibility. Use the documented Compose path as the reference installation and validate any Swarm adaptation separately.&lt;/p&gt;
&lt;h4 id=&quot;what-should-i-verify-before-moving-safeline-into-swarm-&quot;&gt;What should I verify before moving SafeLine into Swarm?&lt;/h4&gt;
&lt;p&gt;Verify the documented host prerequisites, image architecture, persistent access to the SafeLine directory, management port reachability, PostgreSQL password handling, DNS routing, upstream connectivity, and recovery procedures. Also document which Swarm behavior is an assumption because the supplied SafeLine guides do not describe it.&lt;/p&gt;
&lt;h4 id=&quot;how-does-safeline-reach-the-protected-application-&quot;&gt;How does SafeLine reach the protected application?&lt;/h4&gt;
&lt;p&gt;SafeLine acts as an HTTP or HTTPS reverse proxy. In the Web Admin Console, add the application, provide its domain, listening port, SSL setting when applicable, and upstream address. Resolve the domain to the SafeLine server so client traffic reaches the proxy first.&lt;/p&gt;
&lt;h4 id=&quot;what-is-a-practical-first-test-&quot;&gt;What is a practical first test?&lt;/h4&gt;
&lt;p&gt;Use a disposable or low risk application, route a test hostname through SafeLine, confirm normal requests reach the upstream, and then review the console after authorized security tests. Check both blocked requests and legitimate application behavior before expanding the deployment.&lt;/p&gt;
&lt;p&gt;If you are evaluating Docker Swarm, use this checklist with the official deployment and application guides, document every unsupported assumption, and invite your operations team to review the proof of concept. Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; Promotion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31917/how-to-run-safeline-waf-in-docker-swarm-without-skipping-the-deployment-checklist</guid>
<pubDate>Wed, 21 Oct 2026 04:27:21 +0000</pubDate>
</item>
<item>
<title>Securing an Angular Application: Part 3 — Implementing CSP with Nginx</title>
<link>https://coderlegion.com/31601/securing-an-angular-application-part-3-implementing-csp-with-nginx</link>
<description>&lt;p&gt;Continuing my series on &lt;strong&gt;hardening Angular applications&lt;/strong&gt;, Part 3 moves into the actual &lt;strong&gt;Nginx server configuration and Content Security Policy (CSP)&lt;/strong&gt; implementation.&lt;/p&gt;
&lt;p&gt;In this part, I walk through the &lt;code&gt;docker-nginx-server.conf&lt;/code&gt; configuration used to serve a real Angular application and explain how the different pieces work together.&lt;/p&gt;
&lt;p&gt;Some of the topics covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚙️ Angular server configuration with Nginx&lt;/li&gt;
&lt;li&gt;🔀 Angular SPA routing&lt;/li&gt;
&lt;li&gt;📦 Static asset handling and caching&lt;/li&gt;
&lt;li&gt;🚫 Blocking hidden and sensitive files&lt;/li&gt;
&lt;li&gt;🔐 Security headers&lt;/li&gt;
&lt;li&gt;🛡️ Content Security Policy directives&lt;/li&gt;
&lt;li&gt;🔑 Dynamic CSP nonce handling&lt;/li&gt;
&lt;li&gt;🧩 Injecting the nonce into Angular HTML&lt;/li&gt;
&lt;li&gt;⚠️ Error page handling&lt;/li&gt;
&lt;li&gt;🔒 Trusted Types and other CSP protections&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than using a generic CSP policy, the goal is to understand &lt;strong&gt;which resources the Angular application actually needs and how those requirements translate into individual CSP directives&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This part also shows how the CSP nonce is handled using Nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;set $csp_nonce $request_id;

sub_filter_once off;
sub_filter '__CSP_NONCE__' '$csp_nonce';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows the nonce used by the CSP policy to be injected into the Angular application's HTML for each request.&lt;br&gt;
With the Nginx configuration and CSP in place, the next part moves one layer deeper into the deployment process: Dockerizing the Angular application and packaging the Nginx configuration into a production-ready container.&lt;/p&gt;
&lt;p&gt;📖 Read Part 3:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@jangidamit800/angular-content-security-policy-csp-implementing-the-nginx-server-configuration-c70593b9136a?sharedUserId=jangidamit800&quot;&gt;https://medium.com/@jangidamit800/angular-content-security-policy-csp-implementing-the-nginx-server-configuration-c70593b9136a?sharedUserId=jangidamit800&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; #Angular #AngularSecurity #CSP #ContentSecurityPolicy #Nginx #WebSecurity #DevSecOps #Docker #FrontendSecurity&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31601/securing-an-angular-application-part-3-implementing-csp-with-nginx</guid>
<pubDate>Sun, 18 Oct 2026 10:55:08 +0000</pubDate>
</item>
<item>
<title># 🧅 MyZubster: Our First Tor v3 Onion Pilot Is Running!</title>
<link>https://coderlegion.com/31502/myzubster-our-first-tor-v3-onion-pilot-is-running</link>
<description>&lt;h2 id=&quot;-myzubster-our-first-tor-v3-onion-pilot-is-running-&quot;&gt;🧅 MyZubster: Our First Tor v3 Onion Pilot Is Running!&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;From an open-source marketplace to a network of independent services. One step at a time.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hey Coder Legion community! 👋&lt;/p&gt;
&lt;p&gt;We've reached a new technical milestone with &lt;strong&gt;MyZubster&lt;/strong&gt;, our open-source ecosystem focused on connecting people, projects, knowledge, and independent infrastructure.&lt;/p&gt;
&lt;p&gt;We recently deployed our first experimental &lt;strong&gt;Tor v3 Onion Service&lt;/strong&gt; using Docker and HAProxy.&lt;/p&gt;
&lt;h4 id=&quot;-what-works-so-far-&quot;&gt;🚀 What works so far?&lt;/h4&gt;
&lt;p&gt;✅ Tor successfully bootstrapped to 100%&lt;br&gt;
✅ A dedicated Onion v3 identity was generated&lt;br&gt;
✅ An isolated HAProxy service serves our experimental page&lt;br&gt;
✅ Docker networks separate the pilot from production services&lt;br&gt;
✅ The test proxy returns HTTP 404 for &lt;code&gt;/api&lt;/code&gt; and &lt;code&gt;/admin&lt;/code&gt;&lt;br&gt;
✅ The experimental page was displayed during testing&lt;/p&gt;
&lt;p&gt;Our architecture:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tor Browser
     ↓
Tor v3 Onion Service
     ↓
Isolated HAProxy
     ↓
MyZubster Pilot Page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; This is an experimental proof of concept, not a production-ready or independently audited service. Marketplace, Zorgax, wallets, and payment systems are not part of this pilot.&lt;/p&gt;
&lt;h4 id=&quot;-why-are-we-building-this-&quot;&gt;🌍 Why are we building this?&lt;/h4&gt;
&lt;p&gt;We want MyZubster to evolve into an ecosystem where contributors can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect independent open-source projects.&lt;/li&gt;
&lt;li&gt;Host services on their own infrastructure.&lt;/li&gt;
&lt;li&gt;Share verifiable technical knowledge.&lt;/li&gt;
&lt;li&gt;Build interoperable tools.&lt;/li&gt;
&lt;li&gt;Participate in community-driven governance.&lt;/li&gt;
&lt;li&gt;Explore privacy-conscious access through Tor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We believe decentralization should be demonstrated through working infrastructure, not just promises.&lt;/p&gt;
&lt;h4 id=&quot;-what-s-next-&quot;&gt;👨💻 What's next?&lt;/h4&gt;
&lt;p&gt;We're collaborating with our contributor community, including &lt;strong&gt;@wasim-builds&lt;/strong&gt;, on the next proposed development phase:&lt;/p&gt;
&lt;p&gt;🔹 Reproducible Docker Compose deployment&lt;br&gt;
🔹 Tor and HAProxy resilience testing&lt;br&gt;
🔹 Network isolation and security documentation&lt;br&gt;
🔹 Independent code reviews&lt;br&gt;
🔹 Better monitoring and recovery procedures  &lt;/p&gt;
&lt;h4 id=&quot;-developers-wanted-&quot;&gt;🤝 Developers wanted!&lt;/h4&gt;
&lt;p&gt;Are you interested in Docker, Tor, cybersecurity, privacy, distributed systems, or open-source collaboration?&lt;/p&gt;
&lt;p&gt;You can contribute through documentation, testing, code review, or pull requests.&lt;/p&gt;
&lt;p&gt;No VPS or DAO membership is required to get started.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explore the project:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔗 GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🧅 Tor Pilot Discussion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1584&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/issues/1584&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🌐 Website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/&quot;&gt;https://www.myzubster.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let's build independent infrastructure together, one verified component at a time.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31502/myzubster-our-first-tor-v3-onion-pilot-is-running</guid>
<pubDate>Sat, 17 Oct 2026 07:22:23 +0000</pubDate>
</item>
<item>
<title>Building a Decentralized Open-Source Ecosystem: How Nicola and Wasim Are Helping MyZubster Evolve</title>
<link>https://coderlegion.com/31130/building-decentralized-open-source-ecosystem-how-nicola-wasim-helping-myzubster-evolve</link>
<description>&lt;h2 id=&quot;building-a-decentralized-open-source-ecosystem-how-nicola-and-wasim-are-helping-myzubster-evolve&quot;&gt;Building a Decentralized Open-Source Ecosystem: How Nicola and Wasim Are Helping MyZubster Evolve&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What happens when an open-source marketplace starts evolving into a network of independently operated nodes?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's what we're exploring at &lt;a rel=&quot;nofollow&quot; href=&quot;https://myzubster.com&quot;&gt;MyZubster&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We're working toward an ecosystem where developers, creators, researchers, and independent communities can connect their projects, share verifiable knowledge, demonstrate their skills, and eventually operate their own infrastructure.&lt;/p&gt;
&lt;p&gt;Our long-term goal is to reduce dependence on a single server, administrator, or infrastructure provider.&lt;/p&gt;
&lt;p&gt;Two contributors are helping us explore this transition: &lt;strong&gt;Nicola (N4K48)&lt;/strong&gt; and &lt;strong&gt;Wasim (@wasim-builds)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Their work approaches decentralization from two complementary directions.&lt;/p&gt;
&lt;h3 id=&quot;1-nicola-n4k48-independent-node-experiments&quot;&gt;1. Nicola N4K48 — Independent Node Experiments&lt;/h3&gt;
&lt;p&gt;Nicola is experimenting with an independently managed Docker-based node connected to the wider MyZubster ecosystem.&lt;/p&gt;
&lt;p&gt;His work involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker-based services and local agents&lt;/li&gt;
&lt;li&gt;Independent digital catalogs&lt;/li&gt;
&lt;li&gt;Interoperability between contributor projects&lt;/li&gt;
&lt;li&gt;Verifiable technical evidence&lt;/li&gt;
&lt;li&gt;Creative content and NFT provenance experiments&lt;/li&gt;
&lt;li&gt;Authenticated connectivity testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A previous authenticated checkpoint between the MyZubster VPS and the N4K48 node was reported as successful.&lt;/p&gt;
&lt;p&gt;Following credential rotation, the agent has remained offline pending authorization for any further authenticated tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The objective is to demonstrate that contributors can maintain control of their projects while connecting to shared services.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;nicola-s-work-on-github&quot;&gt;Nicola's work on GitHub&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1583&quot;&gt;Independent Node Replication — Issue #1583&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1505&quot;&gt;Pilot Node Network — Issue #1505&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1520&quot;&gt;Contributor Interoperability Bridge — Issue #1520&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1551&quot;&gt;Second Independent Node Checkpoint — Issue #1551&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1499&quot;&gt;Docker Node Reproduction — Issue #1499&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1176&quot;&gt;Nicola Comics / Zorgax Integration — Issue #1176&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-wasim-tor-docker-security-and-interoperability&quot;&gt;2. Wasim — Tor, Docker, Security and Interoperability&lt;/h3&gt;
&lt;p&gt;While Nicola is experimenting with an independent node, Wasim is designing and reviewing components that could make independently operated infrastructure more secure and reproducible.&lt;/p&gt;
&lt;h4 id=&quot;tor-onion-haproxy-docker&quot;&gt;Tor Onion + HAProxy + Docker&lt;/h4&gt;
&lt;p&gt;Wasim has prepared a technical design for a Docker-based architecture involving Tor Onion connectivity and HAProxy.&lt;/p&gt;
&lt;p&gt;The proposal covers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Network architecture and trust boundaries&lt;/li&gt;
&lt;li&gt;Threat modeling&lt;/li&gt;
&lt;li&gt;Isolated Docker environments&lt;/li&gt;
&lt;li&gt;Reproducible tests&lt;/li&gt;
&lt;li&gt;Rollback procedures&lt;/li&gt;
&lt;li&gt;Security evidence&lt;/li&gt;
&lt;li&gt;Infrastructure resilience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;His reported tests were performed locally without exposing host ports, using production credentials, or requiring additional VPS resources.&lt;/p&gt;
&lt;p&gt;The design is still under review. It has not been deployed as a production service.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1584&quot;&gt;Tor/HAProxy/Docker Pilot — Issue #1584&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1610&quot;&gt;Architecture Draft — PR #1610&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;pastebin-github-zorgax&quot;&gt;Pastebin → GitHub → Zorgax&lt;/h4&gt;
&lt;p&gt;Wasim is also working on a proposed evidence-ingestion architecture.&lt;/p&gt;
&lt;p&gt;The idea is to connect external technical information to MyZubster's knowledge system while preserving provenance and security.&lt;/p&gt;
&lt;p&gt;The design includes HTTPS restrictions, redirect blocking, protection against internal-network requests, deterministic rejection tests, and mandatory human review.&lt;/p&gt;
&lt;p&gt;The proposed evidence schema is:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;myzubster.knowledge.evidence.v1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1591&quot;&gt;Interoperability Pilot — Issue #1591&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1611&quot;&gt;Technical Design — PR #1611&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ci-reliability-and-hardhat-3&quot;&gt;CI Reliability and Hardhat 3&lt;/h4&gt;
&lt;p&gt;Wasim has also participated in troubleshooting GitHub Actions workflows and reviewing the migration to Hardhat 3.&lt;/p&gt;
&lt;p&gt;This includes work involving security audits, automated tests, dependency compatibility, and evidence checks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1608&quot;&gt;CI Triage — Issue #1608&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1604&quot;&gt;Hardhat 3 Migration — PR #1604&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/danieldirimini-myzubster/myzubster/pull/32&quot;&gt;Fork Security Audit — PR #32&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wasim also reviewed privacy protections for the Contributor Passport:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1599&quot;&gt;Contributor Passport Privacy Gate — PR #1599&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-the-architecture-we-re-exploring&quot;&gt;3. The Architecture We're Exploring&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                 MyZubster Ecosystem
                         |
             Shared Interoperability
                         |
          +--------------+--------------+
          |              |              |
     N4K48 Node     Future Node    Future Node
          |              |              |
     Docker/Agent    Tor/HAProxy     Independent
     Local Catalog   Docker          Application
          |              |              |
          +--------------+--------------+
                         |
                    Zorgax APIs
                         |
                Knowledge Evidence
                         |
                Contributor Passport
                         |
                    Marketplace
                         |
                  DAO Governance
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;This is a conceptual architecture, not an operational production federation. Wasim's infrastructure remains a design proposal.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We want independent contributors to be able to connect services without giving up ownership or control of their infrastructure.&lt;/p&gt;
&lt;p&gt;The challenge is not simply connecting containers.&lt;/p&gt;
&lt;p&gt;It's designing a system where interoperability, security, evidence, identity, and governance work together.&lt;/p&gt;
&lt;h3 id=&quot;4-why-decentralization-matters-to-us&quot;&gt;4. Why Decentralization Matters to Us&lt;/h3&gt;
&lt;p&gt;Today, many open-source projects depend heavily on a single maintainer.&lt;/p&gt;
&lt;p&gt;That person may be responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hosting costs&lt;/li&gt;
&lt;li&gt;Infrastructure maintenance&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Community coordination&lt;/li&gt;
&lt;li&gt;Funding&lt;/li&gt;
&lt;li&gt;Contributor management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates a technical and organizational single point of failure.&lt;/p&gt;
&lt;p&gt;Our objective is to explore a different approach.&lt;/p&gt;
&lt;p&gt;Independent contributors could eventually operate their own services, maintain their own environments, and communicate through documented interfaces.&lt;/p&gt;
&lt;p&gt;This could reduce centralized infrastructure dependencies and distribute operational responsibility.&lt;/p&gt;
&lt;p&gt;However, &lt;strong&gt;we have not yet demonstrated a stable production federation or fully decentralized governance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We're building toward those goals through smaller, reproducible experiments.&lt;/p&gt;
&lt;h3 id=&quot;5-from-technical-collaboration-to-a-contributor-dao&quot;&gt;5. From Technical Collaboration to a Contributor DAO&lt;/h3&gt;
&lt;p&gt;We're also exploring how a DAO-inspired governance structure could help distribute responsibilities, costs, decisions, and rewards.&lt;/p&gt;
&lt;p&gt;The goal is not to introduce tokens without a purpose.&lt;/p&gt;
&lt;p&gt;We want contributors to have transparent ways to demonstrate their work, participate in decisions, and potentially benefit economically from the value they create.&lt;/p&gt;
&lt;p&gt;This requires careful work on governance, funding, accountability, and dispute resolution.&lt;/p&gt;
&lt;p&gt;Relevant discussions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1554&quot;&gt;DAO Roles and Treasury — Issue #1554&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1560&quot;&gt;Reducing VPS Centralization — Issue #1560&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1580&quot;&gt;Independent Node Operator Program — Issue #1580&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1605&quot;&gt;Contributor Operations — Issue #1605&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are ongoing proposals, not claims that a fully functioning DAO already exists.&lt;/p&gt;
&lt;h3 id=&quot;6-what-s-working-and-what-s-next-&quot;&gt;6. What's Working, and What's Next?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Documented so far:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local Docker and agent tests&lt;/li&gt;
&lt;li&gt;An authenticated N4K48 node checkpoint&lt;/li&gt;
&lt;li&gt;GitHub contributions and code reviews&lt;/li&gt;
&lt;li&gt;Hardhat 3 migration review&lt;/li&gt;
&lt;li&gt;Contributor Passport privacy review&lt;/li&gt;
&lt;li&gt;Isolated Tor/HAProxy design experiments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Still under development:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independent reproducibility reviews&lt;/li&gt;
&lt;li&gt;Secure interoperability between multiple nodes&lt;/li&gt;
&lt;li&gt;Production-grade resilience and failover&lt;/li&gt;
&lt;li&gt;Shared governance procedures&lt;/li&gt;
&lt;li&gt;Sustainable contributor funding&lt;/li&gt;
&lt;li&gt;A fully operational federated network&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our approach is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Propose → Implement → Test → Verify → Document → Improve.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We believe open-source infrastructure should be built around reproducible evidence rather than promises.&lt;/p&gt;
&lt;h3 id=&quot;7-developers-want-to-contribute-&quot;&gt;7. Developers: Want to Contribute?&lt;/h3&gt;
&lt;p&gt;We're looking for developers interested in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker and container infrastructure&lt;/li&gt;
&lt;li&gt;Tor and network resilience&lt;/li&gt;
&lt;li&gt;Node.js and APIs&lt;/li&gt;
&lt;li&gt;Security and CI/CD&lt;/li&gt;
&lt;li&gt;Decentralized architectures&lt;/li&gt;
&lt;li&gt;Knowledge graphs&lt;/li&gt;
&lt;li&gt;Privacy and identity&lt;/li&gt;
&lt;li&gt;Open-source governance&lt;/li&gt;
&lt;li&gt;Reproducible technical experiments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don't need to rent a VPS or pay for infrastructure to start contributing.&lt;/p&gt;
&lt;p&gt;A code review, reproducible local test, documentation improvement, or security assessment can be valuable.&lt;/p&gt;
&lt;h4 id=&quot;explore-the-project&quot;&gt;Explore the project&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;MyZubster&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://myzubster.com&quot;&gt;https://myzubster.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nicola N4K48&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1583&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/issues/1583&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wasim&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1610&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1610&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1611&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1611&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decentralization roadmap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1560&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/issues/1560&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Nicola is helping us explore how an independent contributor project can operate as a node.&lt;/p&gt;
&lt;p&gt;Wasim is helping us design the infrastructure and verification processes that could make such nodes more secure, reproducible, and resilient.&lt;/p&gt;
&lt;p&gt;Together, these efforts are moving MyZubster toward a model where independent developers can collaborate without depending entirely on centralized infrastructure.&lt;/p&gt;
&lt;p&gt;We still have important technical and organizational challenges to solve.&lt;/p&gt;
&lt;p&gt;But the work is happening openly, through GitHub issues, pull requests, reviews, and documented experiments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We're not just trying to connect servers. We're trying to connect independent people, projects, skills, and verifiable knowledge.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you're interested in contributing, reviewing the architecture, or experimenting with an independent node, join the discussion on GitHub.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Daniel Ioni (h4x0r)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Creator &amp;amp; Administrator — MyZubster Ecosystem&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31130/building-decentralized-open-source-ecosystem-how-nicola-wasim-helping-myzubster-evolve</guid>
<pubDate>Thu, 15 Oct 2026 05:27:32 +0000</pubDate>
</item>
<item>
<title>A Weekend Project With Docker Agent: Four Agents, Two Config Files, One Permissions Trap</title>
<link>https://coderlegion.com/30939/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap</link>
<description>&lt;p&gt;Docker Agent lets me describe a team of AI agents, their tools, and their guardrails in one config file that a platform team can review like any other piece of infrastructure. That is the part I like most. It also has a permissions trap that I reproduced in a test, and I'll show it before you copy any example rules.&lt;/p&gt;
&lt;p&gt;I spent a weekend-sized block of time building a small project to find out how far that idea goes. The result is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew&quot;&gt;release-gate-crew&lt;/a&gt;: four agents that review a service's container and CI configuration before release and write a PASS/BLOCK report.&lt;/p&gt;
&lt;h3 id=&quot;what-docker-agent-is&quot;&gt;What Docker Agent is&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/&quot;&gt;Docker Agent&lt;/a&gt; is Docker's open-source runtime for teams of agents. You write a config, run &lt;code&gt;docker agent run crew.yaml&lt;/code&gt;, and talk to the root agent, which delegates to the sub-agents you declared. It ships in Docker Desktop 4.63 and later (versions 4.49 to 4.62 called it &lt;code&gt;cagent&lt;/code&gt;), or as a standalone binary from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/docker/docker-agent/releases&quot;&gt;GitHub releases&lt;/a&gt;. It works with Anthropic, OpenAI, Google, Bedrock, Docker Model Runner and a long list of other providers.&lt;/p&gt;
&lt;p&gt;I used v1.150.0, released 9 October 2026.&lt;/p&gt;
&lt;h3 id=&quot;the-team&quot;&gt;The team&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;              root (lead)                think, todo
             /     |      \
 container_auditor  pipeline_auditor  scribe
 fs: ./target       fs: ./target      fs: ./reports
 shell: grep -n
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;lead&lt;/strong&gt; never reads files. It delegates, then makes the gate call: BLOCK if any finding is high or critical.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;container auditor&lt;/strong&gt; reads &lt;code&gt;target/Dockerfile&lt;/code&gt; and &lt;code&gt;target/compose.yaml&lt;/code&gt;, and may run &lt;code&gt;grep -n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;pipeline auditor&lt;/strong&gt; reads the CI workflow in &lt;code&gt;target/ci/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;scribe&lt;/strong&gt; writes &lt;code&gt;reports/gate-report.md&lt;/code&gt; and can write nowhere else.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;target/&lt;/code&gt; is a deliberately bad sample service: &lt;code&gt;FROM node:latest&lt;/code&gt;, a token baked into &lt;code&gt;ENV&lt;/code&gt;, a privileged container with the Docker socket mounted, and a &lt;code&gt;pull_request_target&lt;/code&gt; workflow that checks out pull request code with &lt;code&gt;permissions: write-all&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-whole-team-is-one-file&quot;&gt;The whole team is one file&lt;/h3&gt;
&lt;p&gt;Here is the core of &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew/blob/main/crew.yaml&quot;&gt;&lt;code&gt;crew.yaml&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;models:
  lead:   { provider: anthropic, model: claude-sonnet-4-5, max_tokens: 8000 }
  worker: { provider: anthropic, model: claude-haiku-4-5,  max_tokens: 4000 }

agents:
  root:
    model: lead
    description: Release gate lead. Delegates reviews and makes the PASS/BLOCK call.
    instruction_file: prompts/lead.md
    sub_agents: [container_auditor, pipeline_auditor, scribe]
    toolsets:
      - type: think
      - type: todo

  container_auditor:
    model: worker
    description: Reviews Dockerfile and compose files for container risks.
    instruction_file: prompts/container_auditor.md
    toolsets:
      - type: filesystem
        allow_list: [&quot;./target&quot;]
      - type: shell

  # pipeline_auditor and scribe follow the same shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sub_agents&lt;/code&gt; line is the whole orchestration. The runtime gives the lead a &lt;code&gt;transfer_task&lt;/code&gt; tool and handles the hand-off. Each agent can use a different model, so the coordinator gets the stronger one and the workers get the cheaper one.&lt;/p&gt;
&lt;p&gt;This is why I prefer config to framework code for this kind of work. In a code-first framework, the same team is classes, a tool registry, a loop, and retry handling, and the permissions live wherever someone remembered to put them. Here the reviewer sees the topology, the tool scope, and the policy in one diff. Swapping a provider is a two-line change. Nothing about the agents is hidden in a helper function.&lt;/p&gt;
&lt;h3 id=&quot;the-same-team-in-hcl&quot;&gt;The same team in HCL&lt;/h3&gt;
&lt;p&gt;Docker Agent also reads &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/configuration/hcl/&quot;&gt;HCL&lt;/a&gt;. If your team writes Terraform, this will feel familiar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;agent &quot;container_auditor&quot; {
  model       = &quot;worker&quot;
  description = &quot;Reviews Dockerfile and compose files for container risks.&quot;
  instruction = file(&quot;prompts/container_auditor.md&quot;)

  toolset &quot;filesystem&quot; {
    allow_list = [&quot;./target&quot;]
  }
  toolset &quot;shell&quot; {}
}

permissions {
  allow = [&quot;read_file&quot;, &quot;read_multiple_files&quot;, &quot;shell:cmd=grep -n *&quot;]
  deny  = [&quot;shell:cmd=*curl*&quot;, &quot;shell:cmd=*&amp;amp;&amp;amp;*&quot;, &quot;shell:cmd=*;*&quot;, &quot;shell:cmd=*|*&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The runtime converts HCL to the YAML shape and validates both against the same schema. Labelled blocks and heredocs suit long prompts and per-agent commands well. Keep your expectations modest, though: there are no variables, locals or modules, and &lt;code&gt;file()&lt;/code&gt; is the only function. It is a second syntax, not a second language. I keep both files in the repo and run the same tests against each, so they cannot drift apart silently.&lt;/p&gt;
&lt;h3 id=&quot;guardrails-live-next-to-the-agents&quot;&gt;Guardrails live next to the agents&lt;/h3&gt;
&lt;p&gt;This is where Docker Agent reads like a DevOps tool rather than an AI demo. Every layer is declared in the same file:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Config &lt;/th&gt;&lt;th&gt;Effect&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Filesystem scope &lt;/td&gt;&lt;td&gt;&lt;code&gt;allow_list&lt;/code&gt; per toolset &lt;/td&gt;&lt;td&gt;Auditors read only &lt;code&gt;./target&lt;/code&gt;. The scribe writes only &lt;code&gt;./reports&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hidden files &lt;/td&gt;&lt;td&gt;&lt;code&gt;.agentsignore&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;, keys and certs disappear from listings, reads and writes.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tool rules &lt;/td&gt;&lt;td&gt;&lt;code&gt;permissions&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Deny, then allow, then ask.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Safety mode &lt;/td&gt;&lt;td&gt;&lt;code&gt;runtime.safety: restricted&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Unattended runs deny anything not explicitly allowed, without prompting.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Spend &lt;/td&gt;&lt;td&gt;&lt;code&gt;budget&lt;/code&gt; &lt;/td&gt;&lt;td&gt;One shared $0.50 and 10-minute ceiling for all four agents.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Isolation &lt;/td&gt;&lt;td&gt;&lt;code&gt;--sandbox&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Runs the team in a Docker Sandboxes VM.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The budget detail is thoughtful. A named budget is one shared pot, so a lead that fans out to ten sub-agents cannot spend ten times the cap.&lt;/p&gt;
&lt;p&gt;Two other features earned their place. &lt;strong&gt;Flavors&lt;/strong&gt; are named patches inside the config, so &lt;code&gt;--flavor cheap&lt;/code&gt; swaps the lead to a smaller model without a second file. &lt;strong&gt;Sharing&lt;/strong&gt; packages the config as an OCI artifact: &lt;code&gt;docker agent share push ./crew.yaml you/release-gate-crew&lt;/code&gt;, then anyone with access can &lt;code&gt;docker agent run you/release-gate-crew&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;testing-the-guardrails-without-an-api-key&quot;&gt;Testing the guardrails without an API key&lt;/h3&gt;
&lt;p&gt;A config file is only as trustworthy as the tests behind it, so I wanted CI to prove the guardrails hold on every push without paying for tokens.&lt;/p&gt;
&lt;p&gt;I wrote a small OpenAI-compatible stub model in Go, standard library only. It plays a fixed script per agent: the lead delegates three times, the auditors read files, and then they deliberately try things they shouldn't. An &lt;code&gt;offline&lt;/code&gt; flavor points every agent at the stub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;flavors:
  offline:
    providers:
      stub:
        api_type: openai_chatcompletions
        base_url: http://127.0.0.1:8089/v1
        token_key: STUB_API_KEY
    models:
      lead:   { provider: stub, model: scripted }
      worker: { provider: stub, model: scripted }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Go integration test then runs the real &lt;code&gt;docker-agent&lt;/code&gt; binary against both &lt;code&gt;crew.yaml&lt;/code&gt; and &lt;code&gt;crew.hcl&lt;/code&gt; and checks each requirement in the project's &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mdabydeen/release-gate-crew/blob/main/SPEC.md&quot;&gt;SPEC.md&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- PASS: TestCrew/crew.yaml/R2_delegates_to_scribe
--- PASS: TestCrew/crew.yaml/R4_.agentsignore_hides_target/.env
--- PASS: TestCrew/crew.yaml/R5_auditor_read_scope_holds
--- PASS: TestCrew/crew.yaml/R6_compound_shell_is_denied
--- PASS: TestCrew/crew.yaml/R7_scribe_cannot_write_target/
--- PASS: TestCrew/crew.yaml/R9_target_unchanged
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What the runtime returned, verbatim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;target/.env&lt;/code&gt;: &lt;code&gt;path &quot;target/.env&quot; is excluded by .agentsignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The pipeline auditor reading a prompt file: &lt;code&gt;outside the allowed directories (./target)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The scribe writing &lt;code&gt;target/Dockerfile&lt;/code&gt;: denied by the restricted safety mode, with no prompt&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep ... &amp;amp;&amp;amp; curl --version&lt;/code&gt;: &lt;code&gt;Tool 'shell' is denied by permissions configuration&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stub proves the harness, not the reviews. For review quality, the repo has three &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.docker.com/ai/docker-agent/features/evaluation/&quot;&gt;&lt;code&gt;docker agent eval&lt;/code&gt;&lt;/a&gt; sessions: the gate must BLOCK the sample and name its worst problems, must not reveal the &lt;code&gt;.env&lt;/code&gt; value when asked directly, and must not claim an edit when told to fix the Dockerfile. Those need real model keys and a container runtime, so they are not in CI yet. I have not run them yet, so I'm making no claim about review quality here.&lt;/p&gt;
&lt;h3 id=&quot;the-permissions-trap&quot;&gt;The permissions trap&lt;/h3&gt;
&lt;p&gt;Permission patterns are globs matched against the whole command string, and &lt;code&gt;*&lt;/code&gt; matches anything, including spaces. That makes the common prefix style unsafe for shell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
  allow: [&quot;shell:cmd=grep -n *&quot;]
  deny:  [&quot;shell:cmd=curl *&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;grep -n FROM target/Dockerfile &amp;amp;&amp;amp; curl --version&lt;/code&gt; starts with &lt;code&gt;grep -n &lt;/code&gt;, so it matches the allow rule. It does not start with &lt;code&gt;curl &lt;/code&gt;, so it misses the deny rule. Deny is checked first, finds nothing, and allow approves it. I reproduced this with the stub (&lt;code&gt;--flavor naive-permissions&lt;/code&gt;): curl ran and printed its version banner, even under &lt;code&gt;restricted&lt;/code&gt; safety, because an explicit allow outranks the safety mode.&lt;/p&gt;
&lt;p&gt;The documentation is clear that permissions are enforced client-side and are not a security boundary, and it points to sandbox mode for isolation. That is fair. The trap is that prefix rules like &lt;code&gt;cat *&lt;/code&gt; and &lt;code&gt;rm *&lt;/code&gt; are the pattern you see most often. What I use instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Deny shell operators outright: &lt;code&gt;*&amp;amp;&amp;amp;*&lt;/code&gt;, &lt;code&gt;*;*&lt;/code&gt;, &lt;code&gt;*|*&lt;/code&gt;, backticks, &lt;code&gt;$(&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write deny rules as &lt;code&gt;*curl*&lt;/code&gt;, not &lt;code&gt;curl *&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Allow one narrow command per agent that needs a shell at all.&lt;/li&gt;
&lt;li&gt;Run anything you don't fully trust with &lt;code&gt;--sandbox&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The test suite asserts the trap still exists, so if a future release starts parsing compound commands, CI will tell me and I'll update the advice.&lt;/p&gt;
&lt;h3 id=&quot;other-rough-edges&quot;&gt;Other rough edges&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The filesystem toolset is unrestricted by default.&lt;/strong&gt; Without an &lt;code&gt;allow_list&lt;/code&gt;, absolute paths and &lt;code&gt;..&lt;/code&gt; reach anything the process can. Docker's own example file calls this a footgun. Set &lt;code&gt;allow_list&lt;/code&gt; on every filesystem toolset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulled configs can set their own safety mode.&lt;/strong&gt; An agent from a registry may declare &lt;code&gt;runtime.safety: autonomous&lt;/code&gt;. Your own &lt;code&gt;--safety&lt;/code&gt; flag or user settings win, but only if you set them. Read a config before you run it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sub-agents get the task text and nothing else.&lt;/strong&gt; I confirmed it: the delegated task arrives in a &lt;code&gt;&amp;lt;task&amp;gt;&lt;/code&gt; block in the sub-agent's system prompt, and its user turn is just &quot;Please proceed.&quot; Isolation is good. It also means the lead's prompt must pass along everything a specialist needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It moves fast.&lt;/strong&gt; The changelog lists 17 releases in September 2026 alone. Pin the version in CI. The rename from &lt;code&gt;cagent&lt;/code&gt; also shows: the config and data directories are still &lt;code&gt;~/.config/cagent&lt;/code&gt; and &lt;code&gt;~/.cagent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetry is on by default.&lt;/strong&gt; The first run says so. Set &lt;code&gt;TELEMETRY_ENABLED=false&lt;/code&gt; in CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evals have prerequisites.&lt;/strong&gt; &lt;code&gt;docker agent eval&lt;/code&gt; runs each case in a container and the default judge model is an OpenAI model, so plan for a container runtime and a second provider key, or pass &lt;code&gt;--judge-model&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/mdabydeen/release-gate-crew &amp;amp;&amp;amp; cd release-gate-crew
make test integration               # Go 1.24+ and docker-agent; no API key needed
export ANTHROPIC_API_KEY=...
docker agent run crew.yaml          # try /gate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Point it at your own service by replacing &lt;code&gt;target/&lt;/code&gt;. If you try the evals with real keys before I do, I'd like to know what the gate gets wrong.&lt;/p&gt;
&lt;p&gt;What I'd like to hear from people running agents in CI: where do your guardrails live today, in the agent's code or next to its config?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/_firelinks/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap-2a10&quot;&gt;DEV&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30939/a-weekend-project-with-docker-agent-four-agents-two-config-files-one-permissions-trap</guid>
<pubDate>Tue, 13 Oct 2026 13:02:21 +0000</pubDate>
</item>
<item>
<title>How I Built a Multi-Tenant LLM Gateway to Prevent Rogue AI Bills (OTel &amp; Hard Limits)</title>
<link>https://coderlegion.com/30908/how-i-built-a-multi-tenant-llm-gateway-to-prevent-rogue-ai-bills-otel-hard-limits</link>
<description>&lt;p&gt;If you are exposing LLMs to multiple users, clients, or internal teams, you are sitting on a ticking time bomb. &lt;/p&gt;
&lt;p&gt;Without strict tenant-level cost attribution and hard API limits, a single rogue user—or a poorly written unhandled script loop—can rack up catastrophic OpenAI or Anthropic bills overnight. Managing these costs is currently a dangerous black box for most engineering teams.&lt;/p&gt;
&lt;p&gt;In this guide, I'll walk you through building a &lt;strong&gt;Multi-Tenant LLM Gateway&lt;/strong&gt; that completely eliminates this risk. We will cover OpenTelemetry (OTel) cost attribution, dynamic rate limiting, multi-provider fallbacks, and visualizing it all in a Streamlit dashboard.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-bottleneck-why-commercial-solutions-fall-short&quot;&gt;1. The Bottleneck: Why Commercial Solutions Fall Short&lt;/h3&gt;
&lt;p&gt;When engineers realize they need to control LLM costs, they usually look at commercial AI gateways. While platforms like Helicone or Portkey are incredibly powerful, they introduce a few bottlenecks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pricing at Scale:&lt;/strong&gt; Paying a percentage of your AI spend or a per-request fee to your gateway quickly becomes more expensive than the LLMs themselves.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missing Hard Limits:&lt;/strong&gt; Many tools offer alerting, but not &lt;em&gt;synchronous hard limits&lt;/em&gt; that instantly cut off a tenant mid-loop before the damage is done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vendor Lock-in &amp;amp; Black Box Analytics:&lt;/strong&gt; Tying your cost attribution to a proprietary dashboard makes it difficult to join tenant AI costs with your primary database (e.g., PostgreSQL billing tables).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By building an internal gateway, you own the traffic, the data, and the circuit breakers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-architecture-step-by-step-breakdown&quot;&gt;2. The Architecture: Step-by-Step Breakdown&lt;/h3&gt;
&lt;p&gt;Our gateway acts as a reverse proxy between your users/applications and the upstream LLM providers. &lt;/p&gt;
&lt;p&gt;Here is the flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Request Interception:&lt;/strong&gt; The gateway (built with Python/FastAPI) receives a standard OpenAI-compatible chat completion request. It extracts the API key to identify the &lt;code&gt;tenant_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard Limit &amp;amp; Redis Check:&lt;/strong&gt; Before forwarding, it queries Redis. If the tenant has exceeded their daily token limit or USD budget, the request is blocked immediately (HTTP 429).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt Sanitization &amp;amp; Routing:&lt;/strong&gt; The gateway cleans the payload (stripping out malicious injections or restricted domains) and determines the provider. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Provider Fallback:&lt;/strong&gt; If OpenAI fails (e.g., 502 Bad Gateway), it seamlessly rewrites the prompt payload and fails over to Anthropic or a Local LLM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OTel Instrumentation:&lt;/strong&gt; Every request generates an OpenTelemetry span containing the &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;prompt_tokens&lt;/code&gt;, and &lt;code&gt;completion_tokens&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard:&lt;/strong&gt; A Streamlit app aggregates the OTel data from your collector/database to visualize spend per tenant.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-code-amp-logic-prompt-structuring-amp-sanitization&quot;&gt;3. The Code &amp;amp; Logic: Prompt Structuring &amp;amp; Sanitization&lt;/h3&gt;
&lt;p&gt;To make this work, the gateway needs to intercept the payload, sanitize inputs (e.g., preventing scraping agents from requesting blacklisted domains), and attach OTel traces.&lt;/p&gt;
&lt;p&gt;Here is a Python snippet demonstrating the gateway routing, domain sanitization, and OTel attribution:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
import json
from fastapi import FastAPI, HTTPException, Request
from opentelemetry import trace
from redis import asyncio as aioredis
import litellm

app = FastAPI()
tracer = trace.get_tracer(__name__)
redis_client = aioredis.from_url(os.getenv(&quot;REDIS_URL&quot;, &quot;redis://localhost:6379&quot;))

# Domain sanitization block (e.g., for AI web scraping agents)
BLACKLISTED_DOMAINS = [&quot;facebook.com&quot;, &quot;instagram.com&quot;]

def sanitize_and_structure_prompt(messages: list) -&amp;gt; list:
    &quot;&quot;&quot;Sanitizes inputs and ensures structure matches fallback requirements.&quot;&quot;&quot;
    for msg in messages:
        if any(domain in msg.get(&quot;content&quot;, &quot;&quot;) for domain in BLACKLISTED_DOMAINS):
            raise ValueError(&quot;Blocked domain requested in prompt payload.&quot;)
    return messages

@app.post(&quot;/v1/chat/completions&quot;)
async def chat_gateway(request: Request):
    payload = await request.json()
    tenant_api_key = request.headers.get(&quot;Authorization&quot;, &quot;&quot;).replace(&quot;Bearer &quot;, &quot;&quot;)
    
    # 1. Identify Tenant &amp;amp; Check Hard Limits in Redis
    tenant_id = await redis_client.hget(&quot;api_keys&quot;, tenant_api_key)
    if not tenant_id:
        raise HTTPException(status_code=401, detail=&quot;Invalid API Key&quot;)
        
    cost_this_month = float(await redis_client.get(f&quot;spend:{tenant_id}&quot;) or 0.0)
    tenant_limit = float(await redis_client.get(f&quot;limit:{tenant_id}&quot;) or 10.0) # $10 limit
    
    if cost_this_month &amp;gt;= tenant_limit:
        raise HTTPException(status_code=429, detail=&quot;Tenant Hard Limit Reached.&quot;)

    # 2. Sanitize Inputs
    try:
        sanitized_messages = sanitize_and_structure_prompt(payload[&quot;messages&quot;])
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # 3. Execute with OTel Tracing &amp;amp; Multi-Provider Fallback
    with tracer.start_as_current_span(&quot;llm_completion&quot;) as span:
        span.set_attribute(&quot;tenant.id&quot;, tenant_id.decode(&quot;utf-8&quot;))
        
        try:
            # LiteLLM handles the multi-provider fallback routing automatically
            response = litellm.completion(
                model=payload.get(&quot;model&quot;, &quot;gpt-4o-mini&quot;),
                messages=sanitized_messages,
                fallbacks=[&quot;anthropic/claude-3-haiku-20240307&quot;],
                metadata={&quot;tenant_id&quot;: tenant_id}
            )
            
            # 4. Extract tokens &amp;amp; calculate cost
            prompt_tokens = response.usage.prompt_tokens
            completion_tokens = response.usage.completion_tokens
            cost = litellm.completion_cost(completion_response=response)
            
            # Update Redis &amp;amp; OTel Span
            await redis_client.incrbyfloat(f&quot;spend:{tenant_id}&quot;, cost)
            span.set_attribute(&quot;llm.cost&quot;, cost)
            span.set_attribute(&quot;llm.usage.prompt_tokens&quot;, prompt_tokens)
            span.set_attribute(&quot;llm.usage.completion_tokens&quot;, completion_tokens)
            
            return response

        except Exception as e:
            span.set_attribute(&quot;error&quot;, True)
            span.record_exception(e)
            raise HTTPException(status_code=500, detail=&quot;LLM Routing Failed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-deployment-amp-performance-timeouts-amp-rate-limits&quot;&gt;4. Deployment &amp;amp; Performance: Timeouts &amp;amp; Rate Limits&lt;/h3&gt;
&lt;p&gt;When dealing with LLM APIs, network timeouts are your biggest enemy. If OpenAI hangs for 60 seconds, your users experience terrible latency. &lt;/p&gt;
&lt;p&gt;To deploy this reliably, you should wrap the entire architecture in a &lt;code&gt;docker-compose.yml&lt;/code&gt; that spins up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The FastAPI Gateway (with &lt;code&gt;gunicorn&lt;/code&gt; and &lt;code&gt;uvicorn&lt;/code&gt; workers).&lt;/li&gt;
&lt;li&gt;A Redis instance for sub-millisecond rate limit checks.&lt;/li&gt;
&lt;li&gt;An OpenTelemetry Collector to batch and export spans asynchronously (so tracing doesn't add latency to the user request).&lt;/li&gt;
&lt;li&gt;A Streamlit dashboard connecting to your metrics datastore (like Prometheus or Postgres).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Handling Timeouts:&lt;/strong&gt; In the gateway layer, configure an aggressive read timeout (e.g., &lt;code&gt;timeout=15&lt;/code&gt;). If the primary model fails to return the first byte within 15 seconds, immediately abort the connection and execute the fallback routing to Anthropic or your local backup model. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-conclusion-amp-ready-to-use-workflow&quot;&gt;5. Conclusion &amp;amp; Ready-to-Use Workflow&lt;/h3&gt;
&lt;p&gt;Building an OTel-instrumented API gateway is the only way to scale AI features across multiple tenants without risking your startup's runway. With a Redis-backed hard limit, you can confidently give API access to your users or agents knowing your max exposure is hard-coded.&lt;/p&gt;
&lt;p&gt;You can easily recreate this architecture using the Python snippets and logic detailed above. &lt;/p&gt;
&lt;p&gt;However, if you want to skip the trial-and-error and get a &lt;strong&gt;turnkey, production-ready package&lt;/strong&gt;—complete with the full Docker Compose setup, multi-provider fallback logic, Streamlit cost dashboard, test fixtures, and raw configurations—you can grab the &lt;strong&gt;Multi-Tenant LLM Gateway Pro&lt;/strong&gt; package below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Instant Access on Whop:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://whop.com/mr-studio-7faa/multi-tenant-llm-gateway-pro-otel-cost-attribution-hard-limit-dashboard&quot;&gt;Get it here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct Download on Gumroad:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/hknevi&quot;&gt;Get it here&lt;/a&gt; &lt;em&gt;(Use promo code &lt;strong&gt;EARLYBIRD&lt;/strong&gt; for 20% off!)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drop any questions about OTel implementation or LiteLLM routing in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-d-autres-outils-mr-studio-qui-pourraient-vous-int-resser-&quot;&gt;🛠️ D'autres outils MR Studio qui pourraient vous intéresser :&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/ttyhmi&quot;&gt;Full-Stack Micro-SaaS Boilerplate — n8n Engine (Auth, DB &amp;amp; AI Pipelines)&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/nqaydc&quot;&gt;Desktop OS Automation Endpoint — TypeSafe OCR &amp;amp; Vision Control for n8n&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/sbtyw&quot;&gt;Enterprise OpenTelemetry LLM Cost Guardrail &amp;amp; Semantic Cache Proxy&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30908/how-i-built-a-multi-tenant-llm-gateway-to-prevent-rogue-ai-bills-otel-hard-limits</guid>
<pubDate>Tue, 13 Oct 2026 08:48:28 +0000</pubDate>
</item>
<item>
<title>Securing an Angular Application: Part 2 — Preparing the Nginx Layer</title>
<link>https://coderlegion.com/30795/securing-an-angular-application-part-2-preparing-the-nginx-layer</link>
<description>&lt;p&gt;Continuing my series on &lt;strong&gt;hardening Angular applications&lt;/strong&gt;, Part 2 moves from browser security concepts into the server layer.&lt;/p&gt;
&lt;p&gt;In this part, I focus on the &lt;strong&gt;Nginx configuration used to serve an Angular application&lt;/strong&gt; and build the foundation required before introducing Content Security Policy (CSP).&lt;/p&gt;
&lt;p&gt;Some of the topics covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚙️ Nginx configuration basics&lt;/li&gt;
&lt;li&gt;📦 Serving Angular static assets&lt;/li&gt;
&lt;li&gt;🗜️ Gzip compression&lt;/li&gt;
&lt;li&gt;💾 Static asset caching&lt;/li&gt;
&lt;li&gt;🔀 Angular SPA routing&lt;/li&gt;
&lt;li&gt;🚫 Restricting access to sensitive files&lt;/li&gt;
&lt;li&gt;⚠️ Error page handling&lt;/li&gt;
&lt;li&gt;🔧 Preparing the Nginx layer for CSP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to understand &lt;strong&gt;why each configuration exists&lt;/strong&gt; rather than simply copying an Nginx configuration from somewhere else.&lt;/p&gt;
&lt;p&gt;This part establishes the Nginx foundation. In the next part, I'll move into the actual &lt;strong&gt;Angular server configuration and CSP implementation&lt;/strong&gt;, including how the policy is built around the application's resource requirements.&lt;/p&gt;
&lt;p&gt;📖 &lt;strong&gt;Read Part 2:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@jangidamit800/angular-csp-configuring-content-security-policy-with-nginx&quot;&gt;https://medium.com/@jangidamit800/angular-csp-configuring-content-security-policy-with-nginx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; #Angular #AngularSecurity #Nginx #CSP #WebSecurity #DevSecOps #FrontendSecurity #Docker&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30795/securing-an-angular-application-part-2-preparing-the-nginx-layer</guid>
<pubDate>Mon, 12 Oct 2026 11:43:40 +0000</pubDate>
</item>
<item>
<title>Angular Security Headers: A Practical Guide to Securing Your Angular Application</title>
<link>https://coderlegion.com/30585/angular-security-headers-a-practical-guide-to-securing-your-angular-application</link>
<description>&lt;p&gt;I've started a new series focused on hardening Angular applications using browser and HTTP security controls.&lt;/p&gt;
&lt;p&gt;In Part 1, I cover the fundamentals of security headers and how they add another layer of protection between an Angular application and the browser.&lt;/p&gt;
&lt;p&gt;Some of the topics covered:&lt;/p&gt;
&lt;p&gt;🔒 HSTS — enforcing HTTPS&lt;br&gt;
🛡️ X-Content-Type-Options&lt;br&gt;
🧭 Referrer-Policy&lt;br&gt;
🎯 Permissions-Policy&lt;br&gt;
🚫 X-Frame-Options&lt;br&gt;
🔄 Cross-Origin Opener Policy (COOP)&lt;br&gt;
🌐 Cross-Origin Resource Policy (CORP)&lt;br&gt;
🔐 Content Security Policy (CSP)&lt;br&gt;
The goal of this series isn't to simply copy a list of security headers, but to understand what each header does, what problem it addresses, and how these controls fit into a real Angular deployment.&lt;/p&gt;
&lt;p&gt;This is Part 1, where we establish the foundation. The upcoming parts will move into the actual CSP, Nginx, and Docker configuration used to serve the application.&lt;/p&gt;
&lt;p&gt;📖 Read Part 1:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@jangidamit800/hardening-an-angular-application-understanding-security-headers-0c65644b981b&quot;&gt;https://medium.com/@jangidamit800/hardening-an-angular-application-understanding-security-headers-0c65644b981b&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Would love to hear how others approach security hardening for Angular applications.&lt;/p&gt;
&lt;p&gt; #Angular #AngularSecurity #WebSecurity #CSP #Nginx #DevSecOps #FrontendSecurity #WebDevelopment&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30585/angular-security-headers-a-practical-guide-to-securing-your-angular-application</guid>
<pubDate>Sat, 10 Oct 2026 12:37:43 +0000</pubDate>
</item>
<item>
<title>SafeLine WAF vs Barracuda WAF: Community Edition vs Appliance-Based Security</title>
<link>https://coderlegion.com/30546/safeline-waf-vs-barracuda-waf-community-edition-vs-appliance-based-security</link>
<description>&lt;h2 id=&quot;safeline-waf-vs-barracuda-waf-community-edition-vs-appliance-based-security&quot;&gt;SafeLine WAF vs Barracuda WAF: Community Edition vs Appliance-Based Security&lt;/h2&gt;
&lt;p&gt;SafeLine and Barracuda WAF both sit in front of your apps and block web attacks — but they're built for different buyers. Here's the practical comparison so you can tell which one matches how you actually work.&lt;/p&gt;
&lt;h3 id=&quot;what-each-one-is&quot;&gt;What each one is&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SafeLine WAF&lt;/strong&gt; is a self-hosted WAF by Chaitin. One command installs it as a reverse proxy that filters HTTP traffic with a semantic engine (SQLi, XSS, bot blocking) and no signature maintenance. The free Community Edition covers 10 apps at 800 QPS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Barracuda WAF&lt;/strong&gt; is an enterprise WAF available as a hardware appliance, virtual appliance, or cloud service. It's sold with support and management features aimed at larger IT teams, and it can be deployed inline or out-of-band.&lt;/p&gt;
&lt;h3 id=&quot;the-difference-delivery-model&quot;&gt;The difference: delivery model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SafeLine is software you run yourself&lt;/strong&gt; as a containerized reverse proxy. Lightweight, free to start, no vendor lock-in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Barracuda is typically an appliance or managed service&lt;/strong&gt;, often purchased with support contracts. Heavier to deploy, but it comes with vendor backing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;side-by-side&quot;&gt;Side by side&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;SafeLine WAF &lt;/th&gt;&lt;th&gt;Barracuda WAF&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Deployment &lt;/td&gt;&lt;td&gt;Self-hosted container / reverse proxy &lt;/td&gt;&lt;td&gt;Appliance, virtual appliance, or cloud&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Detection &lt;/td&gt;&lt;td&gt;Semantic analysis &lt;/td&gt;&lt;td&gt;Signature, behavioral, and anomaly detection&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Management &lt;/td&gt;&lt;td&gt;Web console at :9443 &lt;/td&gt;&lt;td&gt;Management console plus support&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cost &lt;/td&gt;&lt;td&gt;Free Community Edition; paid tiers &lt;/td&gt;&lt;td&gt;License, typically with support&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Footprint &lt;/td&gt;&lt;td&gt;Lightweight, one server &lt;/td&gt;&lt;td&gt;Appliance or dedicated instance&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;which-should-you-start-with-&quot;&gt;Which should you start with?&lt;/h3&gt;
&lt;p&gt;For most self-hosters and small teams, a lightweight self-hosted WAF is enough and far cheaper:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Point it at your app via the console at &lt;code&gt;https://&amp;lt;your-server-ip&amp;gt;:9443&lt;/code&gt; and your traffic is filtered.&lt;/p&gt;
&lt;p&gt;If you're in an environment that mandates an approved appliance with vendor support and integrated management, Barracuda is a conventional enterprise choice.&lt;/p&gt;
&lt;h3 id=&quot;faq&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;can-safeline-replace-an-appliance-waf-&quot;&gt;Can SafeLine replace an appliance WAF?&lt;/h4&gt;
&lt;p&gt;For application-layer protection (SQLi, XSS, bots), yes. If your requirements specifically call for a physical appliance with vendor SLAs, that's a procurement decision SafeLine doesn't aim to replace.&lt;/p&gt;
&lt;h4 id=&quot;does-safeline-need-dedicated-hardware-&quot;&gt;Does SafeLine need dedicated hardware?&lt;/h4&gt;
&lt;p&gt;No. It runs as a container on a server you already have. Barracuda often implies dedicated appliance resources.&lt;/p&gt;
&lt;h4 id=&quot;which-is-faster-to-deploy-&quot;&gt;Which is faster to deploy?&lt;/h4&gt;
&lt;p&gt;SafeLine: one command and a console. Barracuda: provisioning an appliance or instance plus configuration.&lt;/p&gt;
&lt;h4 id=&quot;do-both-block-bots-&quot;&gt;Do both block bots?&lt;/h4&gt;
&lt;p&gt;Yes. SafeLine's semantic engine stops malicious and scraper traffic at the application layer; Barracuda does the same with its own detection stack.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Ready to give SafeLine a try?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⭐ &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;SafeLine WAF on GitHub&lt;/a&gt; — give it a star if you find it useful&lt;/li&gt;
&lt;li&gt;🔗 &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30546/safeline-waf-vs-barracuda-waf-community-edition-vs-appliance-based-security</guid>
<pubDate>Sat, 10 Oct 2026 10:27:48 +0000</pubDate>
</item>
<item>
<title>🧅 MyZubster Is Expanding Toward the Tor Network — Building an Onion Service with Our Open-Source</title>
<link>https://coderlegion.com/30552/myzubster-is-expanding-toward-the-tor-network-building-onion-service-with-our-open-source</link>
<description>&lt;h2 id=&quot;-myzubster-is-expanding-toward-the-tor-network-building-an-onion-service-with-our-open-source-contributors&quot;&gt;🧅 MyZubster Is Expanding Toward the Tor Network — Building an Onion Service with Our Open-Source Contributors&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Docker • Tor • Node.js • React • Nginx • Privacy • Open Source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We're taking an exciting new step in the development of &lt;strong&gt;MyZubster&lt;/strong&gt;: working toward making our ecosystem accessible through the &lt;strong&gt;Tor network using a &lt;code&gt;.onion&lt;/code&gt; address&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Alongside our traditional website, we want to provide an alternative access path designed with privacy, infrastructure isolation, and reliability in mind.&lt;/p&gt;
&lt;p&gt;And we're building it with the help of independent open-source contributors.&lt;/p&gt;
&lt;h3 id=&quot;-recognizing-our-contributors&quot;&gt;👨💻 Recognizing Our Contributors&lt;/h3&gt;
&lt;p&gt;A special thank you to &lt;strong&gt;Wasim (@wasim-builds)&lt;/strong&gt; and &lt;strong&gt;Nicola (N4K48)&lt;/strong&gt; for their contributions to MyZubster's evolving infrastructure.&lt;/p&gt;
&lt;p&gt;Wasim is developing and refining changes involving our Docker setup, backend services, frontend infrastructure, and Tor integration.&lt;/p&gt;
&lt;p&gt;🔗 &lt;strong&gt;Wasim's contribution — PR #1582:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1582&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1582&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;strong&gt;Wasim's GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/wasim-builds&quot;&gt;https://github.com/wasim-builds&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nicola's earlier N4K48 contribution forms an important foundation for the work now being reviewed and tested.&lt;/p&gt;
&lt;p&gt;🔗 &lt;strong&gt;Nicola / N4K48 — Original PR #1457:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1457&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1457&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is what makes open source valuable: contributors bringing independent ideas, improving each other's work, and helping transform experiments into reliable infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;-what-are-we-building-&quot;&gt;🌐 What Are We Building?&lt;/h3&gt;
&lt;p&gt;We're developing a Docker-based Tor Onion Service that would allow users to access the MyZubster frontend through Tor Browser.&lt;/p&gt;
&lt;p&gt;The intended architecture is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;        User with Tor Browser
                 |
             Tor Network
                 |
          .onion Address
                 |
         Tor Onion Service
                 |
       Internal Docker Network
                 |
          Nginx + React
                 |
          Node.js Backend
                 |
              MongoDB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The idea is to provide an alternative entry point without directly exposing backend or database services.&lt;/p&gt;
&lt;p&gt;Our goal is not simply to generate an Onion address. We want the entire access path to be reliable, maintainable, and carefully reviewed.&lt;/p&gt;
&lt;h3 id=&quot;-what-we-ve-already-tested&quot;&gt;✅ What We've Already Tested&lt;/h3&gt;
&lt;p&gt;We've been validating PR #1582 on an isolated VPS using dedicated Docker containers and internal networking.&lt;/p&gt;
&lt;p&gt;Here are some of the results:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backend and APIs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ Node.js backend successfully built and started after a local middleware import correction.&lt;/p&gt;
&lt;p&gt;✅ MongoDB connectivity verified.&lt;/p&gt;
&lt;p&gt;✅ Health, Ledger, and Economics API tests passed.&lt;/p&gt;
&lt;p&gt;✅ Seven targeted authentication and authorization tests passed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Frontend and Nginx&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ React production build completed successfully.&lt;/p&gt;
&lt;p&gt;✅ Frontend Docker image built.&lt;/p&gt;
&lt;p&gt;✅ Nginx configuration validated.&lt;/p&gt;
&lt;p&gt;✅ Homepage returned HTTP 200.&lt;/p&gt;
&lt;p&gt;✅ React SPA fallback worked.&lt;/p&gt;
&lt;p&gt;✅ Nginx successfully proxied Ledger API requests to the backend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tor and Onion Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ Tor Docker image built successfully.&lt;/p&gt;
&lt;p&gt;✅ Tor 0.4.9.11 installed and verified.&lt;/p&gt;
&lt;p&gt;✅ Dedicated non-root Tor user and filesystem permissions tested.&lt;/p&gt;
&lt;p&gt;✅ Frontend reachable from the isolated Onion test container with HTTP 200.&lt;/p&gt;
&lt;p&gt;✅ A candidate fix for Tor's Docker hostname configuration issue passed static validation.&lt;/p&gt;
&lt;p&gt;These are actual results from our isolated test environment, not claims of a completed public launch.&lt;/p&gt;
&lt;h3 id=&quot;-an-interesting-challenge-we-discovered&quot;&gt;🔍 An Interesting Challenge We Discovered&lt;/h3&gt;
&lt;p&gt;The original Onion configuration used the Docker service hostname directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HiddenServicePort 80 frontend:80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During testing, Tor rejected the configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unparseable address in hidden service port configuration.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We reproduced the issue and tested a candidate solution using Docker DNS resolution.&lt;/p&gt;
&lt;p&gt;The frontend hostname was resolved to its internal container IP before generating the Tor configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The modified configuration passed validation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But there's an important challenge:&lt;/p&gt;
&lt;p&gt;Docker container IP addresses can change when services are recreated.&lt;/p&gt;
&lt;p&gt;If Tor uses an address resolved only once at startup, it may later point to the wrong destination.&lt;/p&gt;
&lt;p&gt;This is one of the areas where feedback from experienced infrastructure developers would be especially valuable.&lt;/p&gt;
&lt;h3 id=&quot;-what-we-still-need-to-complete&quot;&gt;🛠️ What We Still Need to Complete&lt;/h3&gt;
&lt;p&gt;Before a public Onion deployment, several tasks remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integrate the tested backend middleware import fix into PR #1582.&lt;/li&gt;
&lt;li&gt;Make Tor destination resolution resilient to Docker container restarts and IP changes.&lt;/li&gt;
&lt;li&gt;Improve service readiness and Tor health checks.&lt;/li&gt;
&lt;li&gt;Remove frontend build instructions that can conceal compilation failures.&lt;/li&gt;
&lt;li&gt;Test real connectivity over the Tor network in a controlled environment.&lt;/li&gt;
&lt;li&gt;Verify browser behavior, privacy boundaries, and end-to-end integration.&lt;/li&gt;
&lt;li&gt;Complete security review before any production deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Our Onion Service is not publicly launched or fully validated yet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We're documenting what works, what doesn't, and what we're learning along the way.&lt;/p&gt;
&lt;h3 id=&quot;-calling-developers-who-want-to-contribute&quot;&gt;🤝 Calling Developers Who Want to Contribute&lt;/h3&gt;
&lt;p&gt;We're particularly interested in connecting with developers experienced in:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tor Onion Services, Docker Compose, Linux infrastructure, Node.js, React, Nginx, service discovery, and application security.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's a question for the community:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How would you design a reliable Tor Onion Service behind Docker when the frontend container's IP address may change?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Would you use a local reverse proxy, a service-discovery mechanism, automatic configuration reloads, or a different architecture?&lt;/p&gt;
&lt;p&gt;We'd love to hear about approaches you've implemented or tested.&lt;/p&gt;
&lt;h3 id=&quot;-join-the-myzubster-open-source-journey&quot;&gt;🚀 Join the MyZubster Open-Source Journey&lt;/h3&gt;
&lt;p&gt;We're building an ecosystem where contributors can experiment, review each other's ideas, and improve real infrastructure together.&lt;/p&gt;
&lt;p&gt;Explore the project:&lt;/p&gt;
&lt;p&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/&quot;&gt;https://www.myzubster.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;💻 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🔧 &lt;strong&gt;Active contribution:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1582&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1582&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🧩 &lt;strong&gt;Original N4K48 contribution:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/pull/1457&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/pull/1457&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thank you again to &lt;strong&gt;Wasim and Nicola&lt;/strong&gt; for their work and willingness to contribute to the ecosystem.&lt;/p&gt;
&lt;p&gt;We're not announcing a finished Onion deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We're inviting the developer community to help us build one properly.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because open source grows through collaboration, evidence, and shared progress.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MyZubster — Building together, testing together, growing together.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;opensource-tor-docker-privacy-nodejs-react-linux-webdevelopment&quot;&gt;OpenSource #Tor #Docker #Privacy #NodeJS #React #Linux #WebDevelopment&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30552/myzubster-is-expanding-toward-the-tor-network-building-onion-service-with-our-open-source</guid>
<pubDate>Sat, 10 Oct 2026 04:14:28 +0000</pubDate>
</item>
<item>
<title>Building ZORGAX: Turning a Local LLM into a DevOps AI Agent</title>
<link>https://coderlegion.com/30386/building-zorgax-turning-a-local-llm-into-a-devops-ai-agent</link>
<description>&lt;h2 id=&quot;building-zorgax-turning-a-local-llm-into-a-devops-ai-agent&quot;&gt;Building ZORGAX: Turning a Local LLM into a DevOps AI Agent&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What we learned connecting Ollama, Docker, Open WebUI, and real tool calls inside the MyZubster ecosystem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What happens when you want your local AI to do more than answer questions?&lt;/p&gt;
&lt;p&gt;That's the challenge we're exploring with &lt;strong&gt;ZORGAX&lt;/strong&gt;, an AI assistant being developed within the MyZubster ecosystem.&lt;/p&gt;
&lt;p&gt;Our goal is to move from a conversational language model toward a controlled infrastructure agent that can monitor servers, inspect services, diagnose failures, and eventually assist with approved maintenance operations.&lt;/p&gt;
&lt;p&gt;We're not building an unrestricted autonomous system.&lt;/p&gt;
&lt;p&gt;We're building something that must earn trust through testing, restricted permissions, and predictable behavior.&lt;/p&gt;
&lt;p&gt;And during our latest experiments, we encountered a problem that illustrates exactly why that distinction matters.&lt;/p&gt;
&lt;h3 id=&quot;the-environment&quot;&gt;The environment&lt;/h3&gt;
&lt;p&gt;Our current pilot runs on an Ubuntu VPS and brings together several components:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ollama &lt;/td&gt;&lt;td&gt;Local LLM inference&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Qwen 2.5 3B &lt;/td&gt;&lt;td&gt;Foundation language model&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ZORGAX &lt;/td&gt;&lt;td&gt;Custom AI assistant&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Open WebUI &lt;/td&gt;&lt;td&gt;Conversational interface&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Docker Compose &lt;/td&gt;&lt;td&gt;Service orchestration&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;N4K48 &lt;/td&gt;&lt;td&gt;Independent AI integration pilot&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Qdrant &lt;/td&gt;&lt;td&gt;Vector infrastructure&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;systemd &lt;/td&gt;&lt;td&gt;Host-level service management&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ZORGAX currently uses a custom Ollama model built on &lt;code&gt;qwen2.5:3b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We chose to start with a relatively small model to explore what could be achieved with local inference and controlled tools.&lt;/p&gt;
&lt;h3 id=&quot;challenge-1-connecting-containers-to-ollama&quot;&gt;Challenge 1: Connecting containers to Ollama&lt;/h3&gt;
&lt;p&gt;Our first problem was networking.&lt;/p&gt;
&lt;p&gt;Ollama was listening on:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;172.22.0.1:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, Open WebUI was configured to connect through:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://host.docker.internal:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the container, &lt;code&gt;host.docker.internal&lt;/code&gt; resolved to &lt;code&gt;172.17.0.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Requests to that address timed out.&lt;/p&gt;
&lt;p&gt;After verifying connectivity from inside both the N4K48 API and Open WebUI containers, we updated Open WebUI to use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;OLLAMA_BASE_URL: &quot;http://172.22.0.1:11434&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also configured a systemd socket proxy for host-local access through &lt;code&gt;127.0.0.1:11434&lt;/code&gt;, while preserving the existing Docker-facing Ollama endpoint.&lt;/p&gt;
&lt;p&gt;The result was successful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP: 200
MODELS:
- nomic-embed-text:latest
- zorgax:latest
- qwen2.5:3b

CONNECTION: OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open WebUI also passed its healthcheck:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;running / healthy
HTTP 200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We had established reliable connectivity between the application containers and the local AI service.&lt;/p&gt;
&lt;h3 id=&quot;challenge-2-from-chat-to-tool-calling&quot;&gt;Challenge 2: From chat to tool calling&lt;/h3&gt;
&lt;p&gt;Next, we tested ZORGAX's ability to interact with external functions.&lt;/p&gt;
&lt;p&gt;We asked the assistant to identify available capabilities and distinguish verified integrations from planned ones.&lt;/p&gt;
&lt;p&gt;ZORGAX invoked a function named:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;list_automations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tool returned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;automations&quot;: [],
  &quot;total&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A reasonable next step would be to report that the query returned no active automations.&lt;/p&gt;
&lt;p&gt;Instead, ZORGAX repeated the function call.&lt;/p&gt;
&lt;p&gt;We observed a sequence of 28 tool invocations, followed by another test that reached 16 calls.&lt;/p&gt;
&lt;p&gt;This was the most valuable finding of the experiment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A model that can call a function is not necessarily an agent that can manage a function safely.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tool calling involves much more than producing correctly formatted JSON.&lt;/p&gt;
&lt;p&gt;The agent must understand when the task is complete, avoid unnecessary retries, and behave predictably when tools return empty or unexpected results.&lt;/p&gt;
&lt;h3 id=&quot;challenge-3-isolating-the-root-cause&quot;&gt;Challenge 3: Isolating the root cause&lt;/h3&gt;
&lt;p&gt;Rather than immediately changing the model or restarting services, we tested the behavior directly through Ollama's API.&lt;/p&gt;
&lt;p&gt;First, we asked ZORGAX how it should respond if an automation search returned zero results.&lt;/p&gt;
&lt;p&gt;Without any tools enabled, the model provided an appropriate response and completed generation successfully.&lt;/p&gt;
&lt;p&gt;Then we simulated a complete function-calling interaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1 — User request&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;How many active automations are there?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2 — Model requests a tool&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;list_automations&quot;,
  &quot;arguments&quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 3 — Simulated tool response&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;automations&quot;: [],
  &quot;total&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 4 — Final assistant response&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;There are currently no active automations.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No additional tool call was generated.&lt;/p&gt;
&lt;p&gt;This controlled test demonstrated that ZORGAX could successfully complete a tool interaction.&lt;/p&gt;
&lt;p&gt;The repeated calls seen in Open WebUI therefore remain an integration-level issue to investigate. They could involve the model's behavior under different prompts, tool execution configuration, or the way tool responses are handled.&lt;/p&gt;
&lt;p&gt;We haven't established a definitive root cause yet.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-for-devops-agents&quot;&gt;Why this matters for DevOps agents&lt;/h3&gt;
&lt;p&gt;Imagine extending the same behavior to real infrastructure commands.&lt;/p&gt;
&lt;p&gt;A repeated read-only status query might consume unnecessary resources.&lt;/p&gt;
&lt;p&gt;But a repeated restart, deployment, configuration update, or database operation could have much more serious consequences.&lt;/p&gt;
&lt;p&gt;That is why our design direction emphasizes several safeguards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Least-privilege access:&lt;/strong&gt; tools receive only the permissions required.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-only first:&lt;/strong&gt; monitoring before operational control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bounded execution:&lt;/strong&gt; limits on calls, retries, and execution time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplicate prevention:&lt;/strong&gt; repeated requests should not automatically trigger repeated actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human approval:&lt;/strong&gt; consequential changes require explicit authorization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditability:&lt;/strong&gt; operations need traceable inputs, results, and outcomes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These protections must exist in the execution layer, not merely as instructions inside the model's prompt.&lt;/p&gt;
&lt;h3 id=&quot;next-milestone-myzubster-server-status&quot;&gt;Next milestone: &lt;code&gt;myzubster_server_status&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Our next planned integration is a read-only infrastructure monitoring tool.&lt;/p&gt;
&lt;p&gt;The proposed &lt;code&gt;myzubster_server_status&lt;/code&gt; interface would collect a restricted set of system information, including:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;server&quot;: &quot;myzubster&quot;,
  &quot;services&quot;: {
    &quot;ollama&quot;: &quot;running&quot;,
    &quot;docker&quot;: &quot;running&quot;,
    &quot;ipfs&quot;: &quot;running&quot;
  },
  &quot;health&quot;: &quot;ok&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;This JSON is an illustrative target format, not a live measurement.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first implementation will focus on retrieving selected health information without granting the model shell access, root permissions, or direct access to the Docker socket.&lt;/p&gt;
&lt;p&gt;Once implemented and tested, it could enable prompts such as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;ZORGAX, check the MyZubster infrastructure and explain whether any critical services require attention.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Later, we want to expand toward bounded log diagnostics, repository health, test results, and controlled maintenance workflows.&lt;/p&gt;
&lt;h3 id=&quot;what-we-ve-learned-so-far&quot;&gt;What we've learned so far&lt;/h3&gt;
&lt;p&gt;Three lessons stand out from this development session.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Network connectivity is part of AI engineering.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An LLM can be perfectly healthy while the applications using it cannot reach its endpoint.&lt;/p&gt;
&lt;p&gt;Testing from inside containers matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Tool calling requires workflow discipline.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Generating a valid function call is only the beginning. Reliable completion and retry behavior are equally important.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Agent safety is an architectural concern.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Prompts alone cannot enforce operational safety. Permissions, execution limits, validation, and approvals belong in the surrounding software.&lt;/p&gt;
&lt;h3 id=&quot;where-zorgax-stands-today&quot;&gt;Where ZORGAX stands today&lt;/h3&gt;
&lt;p&gt;ZORGAX can now communicate through Open WebUI, use the local Ollama inference service, and successfully demonstrate a controlled function-calling cycle.&lt;/p&gt;
&lt;p&gt;The infrastructure connectivity is verified.&lt;/p&gt;
&lt;p&gt;However, the repeated tool-call behavior observed in Open WebUI still needs investigation before broader operational tools are enabled.&lt;/p&gt;
&lt;p&gt;Our next step is to build the monitoring layer and make the execution workflow reliable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The long-term goal is not an AI that controls everything. It's an AI that understands what it is allowed to inspect, what it can recommend, and when it must ask for permission.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's the direction we're taking with ZORGAX and MyZubster.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; MyZubster Ecosystem&lt;br&gt;
&lt;strong&gt;AI Agent:&lt;/strong&gt; ZORGAX&lt;br&gt;
&lt;strong&gt;Integration Pilot:&lt;/strong&gt; N4K48&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Ollama, Qwen 2.5, Open WebUI, Docker, Python, Ubuntu&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Work in progress. Current capabilities are experimental and should not be interpreted as production-ready autonomous infrastructure management.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30386/building-zorgax-turning-a-local-llm-into-a-devops-ai-agent</guid>
<pubDate>Fri, 09 Oct 2026 03:43:38 +0000</pubDate>
</item>
<item>
<title>Deploying a Production-Ready Go Scraper to Kubernetes with Prometheus: The Complete Implementation</title>
<link>https://coderlegion.com/29677/deploying-production-ready-scraper-kubernetes-with-prometheus-complete-implementation</link>
<description>&lt;p&gt;Most Go tutorials stop at &lt;code&gt;go run main.go&lt;/code&gt;. Real production starts after that.&lt;/p&gt;
&lt;p&gt;I had a working Go Jobs Tracker with Prometheus metrics, but it only ran on my laptop. If I close the lid, production dies. That is not production.&lt;/p&gt;
&lt;p&gt;This is how I took it from localhost to a scalable, observable Kubernetes deployment with 12MB images, health checks, and zero-downtime.&lt;/p&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;The Problem: Why &lt;code&gt;go run&lt;/code&gt; is Not Production&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;No isolation&lt;/li&gt;
&lt;li&gt;No auto-restart&lt;/li&gt;
&lt;li&gt;No scaling&lt;/li&gt;
&lt;li&gt;No observability in cluster&lt;/li&gt;
&lt;li&gt;Secrets in .env&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need Docker plus Kubernetes plus Prometheus.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Production Dockerfile - Multi-Stage (12MB vs 1GB)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the Dockerfile big tech uses. Not the junior FROM golang.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;Dockerfile&lt;/code&gt; in root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;Stage 1: Builder - we compile here
FROM golang:1.22-alpine AS builder

WORKDIR /app

Cache dependencies first - faster rebuilds
COPY go.mod go.sum ./
RUN go mod download

COPY . .

Build static binary - no CGO, no libc dependency
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
  -ldflags=&quot;-w -s&quot; \
  -o /go-jobs-tracker ./cmd/scraper

Stage 2: Final - distroless-level small
FROM alpine:3.19

WORKDIR /app

RUN apk --no-cache add ca-certificates wget

Copy only the binary
COPY --from=builder /go-jobs-tracker .

Non-root user - security best practice
RUN adduser -D appuser
USER appuser

EXPOSE 2112

K8s needs this to know if you are alive
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:2112/healthz || exit 1

CMD [&quot;./go-jobs-tracker&quot;]
Why this is production grade:
- Multi-stage results in 12MB final image
- -ldflags=&quot;-w -s&quot; strips debug symbols
- CGO_ENABLED=0 produces static binary
- Non-root user for security
- HEALTHCHECK for Docker and K8s

Build it:
docker build -t go-jobs-tracker:latest .
docker images | grep go-jobs-tracker
3. Full Observability Stack with docker-compose

One command to run everything like in production.

Create `docker-compose.yml`:
version: '3.8'
services:
  scraper:
    build: .
    ports:
            - &quot;2112:2112&quot;
    restart: unless-stopped
    environment:
            - SERPAPI_KEY=${SERPAPI_KEY}
    networks:
            - monitoring

  prometheus:
    image: prom/prometheus:v2.52.0
    ports:
            - &quot;9090:9090&quot;
    volumes:
            - ./prometheus.yml:/etc/prometheus/prometheus.yml
            - prometheus_data:/prometheus
    command:
            - '--config.file=/etc/prometheus/prometheus.yml'
            - '--storage.tsdb.path=/prometheus'
    networks:
            - monitoring

  grafana:
    image: grafana/grafana:10.4.0
    ports:
            - &quot;3000:3000&quot;
    environment:
            - GF_SECURITY_ADMIN_USER=admin
            - GF_SECURITY_ADMIN_PASSWORD=admin
            - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
            - grafana_data:/var/lib/grafana
    depends_on:
            - prometheus
    networks:
            - monitoring

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge
Create `prometheus.yml`:
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
    - job_name: 'go-jobs-tracker'
    static_configs:
            - targets: ['scraper:2112']
    metrics_path: /metrics
    scrape_interval: 5s
Run it:
docker-compose up --build
Your stack:
- App on 2112 slash metrics
- Prometheus on 9090
- Grafana on 3000 with admin slash admin

4. Kubernetes - Real Production

This is what separates Senior from Staff.

Create folder `k8s` and file `k8s/deployment.yaml`:
apiVersion: v1
kind: Secret
metadata:
  name: scraper-secrets
type: Opaque
stringData:
  serpapi-key: YOUR_SERPAPI_KEY_HERE

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-jobs-tracker
  labels:
    app: go-jobs-tracker
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: go-jobs-tracker
  template:
    metadata:
      labels:
        app: go-jobs-tracker
      annotations:
        prometheus.io/scrape: &quot;true&quot;
        prometheus.io/port: &quot;2112&quot;
        prometheus.io/path: &quot;/metrics&quot;
    spec:
      containers:
            - name: scraper
        image: ghcr.io/aureliopires186/go-jobs-tracker:latest
        imagePullPolicy: Always
        ports:
                - name: metrics
          containerPort: 2112
        env:
                - name: SERPAPI_KEY
          valueFrom:
            secretKeyRef:
              name: scraper-secrets
              key: serpapi-key
        resources:
          requests:
            memory: &quot;64Mi&quot;
            cpu: &quot;50m&quot;
          limits:
            memory: &quot;128Mi&quot;
            cpu: &quot;200m&quot;
        livenessProbe:
          httpGet:
            path: /healthz
            port: 2112
          initialDelaySeconds: 15
          periodSeconds: 20
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /healthz
            port: 2112
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: go-jobs-tracker-service
  labels:
    app: go-jobs-tracker
spec:
  selector:
    app: go-jobs-tracker
  ports:
    - name: metrics
    port: 2112
    targetPort: 2112
    protocol: TCP
  type: ClusterIP
Deploy to K8s:
kubectl apply -f k8s/deployment.yaml
kubectl get pods -w
kubectl logs -f deployment/go-jobs-tracker
kubectl port-forward svc/go-jobs-tracker-service 2112:2112
5. The Metrics Code - What Makes It Observable

Your metrics endpoint should expose this:
// metrics/metrics.go
package metrics

import (
  &quot;github.com/prometheus/client_golang/prometheus&quot;
  &quot;github.com/prometheus/client_golang/prometheus/promauto&quot;
)

var (
  JobsScraped = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: &quot;jobs_scraped_total&quot;,
    Help: &quot;Total number of jobs scraped&quot;,
  }, []string{&quot;engine&quot;, &quot;status&quot;})

  ScrapeDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
    Name:    &quot;scrape_duration_seconds&quot;,
    Help:    &quot;Duration of scrape jobs&quot;,
    Buckets: prometheus.DefBuckets,
  }, []string{&quot;engine&quot;})

  ScraperErrors = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: &quot;scraper_errors_total&quot;,
    Help: &quot;Total errors in scraper&quot;,
  }, []string{&quot;type&quot;})
)
And in http://main.go:
http.Handle(&quot;/metrics&quot;, promhttp.Handler())
http.HandleFunc(&quot;/healthz&quot;, func(w http.ResponseWriter, r *http.Request){
  w.WriteHeader(200)
  w.Write([]byte(&quot;ok&quot;))
})
go http.ListenAndServe(&quot;:2112&quot;, nil)
6. Result: What You Get

After this tutorial you have:

- 12MB Docker image, not 1GB
- Non-root, secure, production-ready
- docker-compose with full monitoring
- K8s with 2 replicas, zero-downtime rolling update
- Liveness and Readiness probes
- Resource limits to prevent OOM in prod
- Prometheus auto-discovery
- Secrets, not hardcoded keys

This is how real companies deploy Go in production.

Production-ready Go is not about code, it is about deployment.


•Author : Pires Aurélio 
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29677/deploying-production-ready-scraper-kubernetes-with-prometheus-complete-implementation</guid>
<pubDate>Sun, 04 Oct 2026 01:15:51 +0000</pubDate>
</item>
<item>
<title>Building MyZubster with Nicola: Connecting Two Independent Systems Without Exposing a Local Computer</title>
<link>https://coderlegion.com/29494/building-myzubster-nicola-connecting-independent-systems-without-exposing-local-computer</link>
<description>&lt;p&gt;Building MyZubster with Nicola: Connecting Two Independent Systems Without Exposing a Local Computer&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can two developers collaborate through independent environments without exposing their local infrastructure to the public internet?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is one of the questions we're exploring with MyZubster.&lt;/p&gt;
&lt;p&gt;I'm working with Nicola (N4K48), a contributor who has been developing his own local MyZubster environment, including the Nicola Comics pilot. We're gradually connecting our work across Docker, knowledge graphs, verifiable evidence and decentralized technologies.&lt;/p&gt;
&lt;p&gt;Rather than building everything at once, we're following an incremental approach: implement, test, document and verify.&lt;/p&gt;
&lt;h3 id=&quot;1-our-latest-experiment-myzubster-node-bridge&quot;&gt;1. Our latest experiment: MyZubster Node Bridge&lt;/h3&gt;
&lt;p&gt;We've developed a small Python-based communication bridge running inside Docker on a VPS.&lt;/p&gt;
&lt;p&gt;The architecture is deliberately simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nicola's computer → HTTPS → Nginx → Python Node Bridge&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The bridge uses two separate authentication roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A node token for retrieving requests and submitting results.&lt;/li&gt;
&lt;li&gt;An administrative token for creating requests and checking their status.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nginx exposes only the endpoints needed by the remote node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /node/next&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /node/result&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Administrative endpoints remain inaccessible through the public reverse proxy.&lt;/p&gt;
&lt;p&gt;The bridge listens locally on the VPS, and HTTPS is enabled using Let's Encrypt.&lt;/p&gt;
&lt;p&gt;We've already verified the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker container health checks.&lt;/li&gt;
&lt;li&gt;Local broker communication.&lt;/li&gt;
&lt;li&gt;Simulated request and response handling.&lt;/li&gt;
&lt;li&gt;HTTPS certificate installation.&lt;/li&gt;
&lt;li&gt;Authentication enforcement.&lt;/li&gt;
&lt;li&gt;Blocking of administrative endpoints.&lt;/li&gt;
&lt;li&gt;Public domain connectivity from the VPS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our next step is to connect Nicola's actual computer and validate the complete communication cycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An important distinction:&lt;/strong&gt; our simulated tests were successful, but we haven't yet completed the end-to-end test between the two physical environments.&lt;/p&gt;
&lt;h3 id=&quot;2-beyond-infrastructure-building-verifiable-knowledge&quot;&gt;2. Beyond infrastructure: building verifiable knowledge&lt;/h3&gt;
&lt;p&gt;Our collaboration also extends to MyZubster's Knowledge Graph.&lt;/p&gt;
&lt;p&gt;Nicola recently worked on improving how evidence is synchronized, identified and displayed.&lt;/p&gt;
&lt;p&gt;Our MYZ-209 work included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stable identifiers for evidence.&lt;/li&gt;
&lt;li&gt;Deduplication.&lt;/li&gt;
&lt;li&gt;Knowledge Card synchronization.&lt;/li&gt;
&lt;li&gt;Preserving historical Proof v2 attestations.&lt;/li&gt;
&lt;li&gt;Displaying Proof v3 as a separate, identifiable attestation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The changes were merged, and Nicola subsequently confirmed the expected behavior in production.&lt;/p&gt;
&lt;p&gt;We're also documenting our shared learning activities involving Docker, Ethereum Sepolia, Base Sepolia, MetaMask and Zorgax.&lt;/p&gt;
&lt;p&gt;An important principle guides this work:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Documented technical activity is evidence of an activity, not automatic certification of expertise.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We distinguish independently verifiable technical results from personal learning experiences and activities that still require confirmation.&lt;/p&gt;
&lt;h3 id=&quot;3-nicola-comics-a-practical-integration-scenario&quot;&gt;3. Nicola Comics: a practical integration scenario&lt;/h3&gt;
&lt;p&gt;Nicola's local comics catalog gives us a concrete application for testing the bridge.&lt;/p&gt;
&lt;p&gt;Our initial goal is to request catalog information remotely while keeping the underlying local environment under Nicola's control.&lt;/p&gt;
&lt;p&gt;We're starting with simple operations such as retrieving the gallery and requesting comic details.&lt;/p&gt;
&lt;p&gt;Future experiments may explore how comics, knowledge records and blockchain-based attestations could interact.&lt;/p&gt;
&lt;p&gt;However, a technical demonstration is not equivalent to publishing an NFT or establishing ownership rights. Those would require separate verification and explicit authorization.&lt;/p&gt;
&lt;h3 id=&quot;4-what-we-re-learning&quot;&gt;4. What we're learning&lt;/h3&gt;
&lt;p&gt;This project is raising several interesting engineering questions.&lt;/p&gt;
&lt;p&gt;How much functionality should a small communication bridge expose?&lt;/p&gt;
&lt;p&gt;How can we establish trust between independent environments without opening local services to the internet?&lt;/p&gt;
&lt;p&gt;How do we document a successful experiment without overstating what it proves?&lt;/p&gt;
&lt;p&gt;And how can contributors retain control over their environments while participating in a shared ecosystem?&lt;/p&gt;
&lt;p&gt;Our approach is to keep the integration small, authentication explicit, administrative access restricted and experimental results clearly documented.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next milestone is a real end-to-end test from Nicola's computer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'd be interested to hear how other developers approach secure communication between independently managed Docker environments.&lt;/p&gt;
&lt;p&gt;Would you use a lightweight HTTPS polling bridge, a message broker or another architecture?&lt;/p&gt;
&lt;h2 id=&quot;myzubster-docker-opensource-cybersecurity-python-knowledgegraph-web3-softwaredevelopment&quot;&gt;MyZubster #Docker #OpenSource #Cybersecurity #Python #KnowledgeGraph #Web3 #SoftwareDevelopment&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29494/building-myzubster-nicola-connecting-independent-systems-without-exposing-local-computer</guid>
<pubDate>Fri, 02 Oct 2026 07:09:17 +0000</pubDate>
</item>
<item>
<title>Docker for Beginners</title>
<link>https://coderlegion.com/28714/docker-for-beginners</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;If you’ve ever built an application that worked perfectly on your computer but failed on another machine, then you’ve experienced one of the biggest problems in software development “inconsistent environments”.&lt;/p&gt;
&lt;p&gt;Maybe your project depended on a specific version of Python, Node.js, Java, or a database that wasn’t installed on another computer. Developers often spend hours trying to recreate the same development environment before they can even begin writing code.&lt;/p&gt;
&lt;p&gt;This is exactly the problem &lt;em&gt;Docker&lt;/em&gt; solves.&lt;/p&gt;
&lt;p&gt;Docker allows developers to package an application together with everything it needs to run, the code, libraries, dependencies, configurations, and runtime into a portable package called a container.&lt;/p&gt;
&lt;p&gt;The result is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Build once, run anywhere.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whether you’re running your application on Windows, macOS, Linux, a cloud server, or a teammate’s laptop, Docker helps ensure it behaves the same way.&lt;/p&gt;
&lt;h3 id=&quot;containerization&quot;&gt;Containerization&lt;/h3&gt;
&lt;p&gt;Containerization is a method of packaging software so it can run consistently across different environments.&lt;/p&gt;
&lt;p&gt;Instead of installing every dependency manually on each machine, everything the application needs is bundled together inside a container. No matter what application is inside, Docker knows how to run it consistently.&lt;/p&gt;
&lt;h3 id=&quot;docker&quot;&gt;Docker&lt;/h3&gt;
&lt;p&gt;Docker is an open source platform that helps developers build, package, distribute, and run applications inside containers.&lt;/p&gt;
&lt;p&gt;Docker provides all the tools needed to create containers, manage them, and deploy applications with minimal setup. Instead of worrying about operating systems, dependencies, or configuration differences, developers focus on writing code.&lt;/p&gt;
&lt;h3 id=&quot;docker-architecture&quot;&gt;Docker Architecture&lt;/h3&gt;
&lt;p&gt;Docker is made up of several important components. Understanding these components makes Docker much easier to learn.&lt;/p&gt;
&lt;h3 id=&quot;docker-engine&quot;&gt;Docker Engine&lt;/h3&gt;
&lt;p&gt;The Docker Engine is the core software that powers Docker. Think of Docker Engine as the “brain” of Docker.&lt;/p&gt;
&lt;p&gt;It is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building Docker images&lt;/li&gt;
&lt;li&gt;Creating containers&lt;/li&gt;
&lt;li&gt;Running containers&lt;/li&gt;
&lt;li&gt;Managing networks&lt;/li&gt;
&lt;li&gt;Managing storage volumes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whenever you type a Docker command like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker Engine receives the command and performs all the necessary operations behind the scenes.&lt;/p&gt;
&lt;p&gt;Without Docker Engine, Docker cannot function.&lt;/p&gt;
&lt;h2 id=&quot;docker-image&quot;&gt;Docker Image&lt;/h2&gt;
&lt;p&gt;A Docker Image is a blueprint or template used to create containers. Images are read only. You don’t run an image directly. Instead, Docker creates a container from an image.&lt;/p&gt;
&lt;p&gt;It contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application code&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Libraries&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Configuration files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One Docker image can create many containers.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker pull nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This downloads the official Nginx image.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*in-bbOqK2GCmBhjQ.png&quot; alt=&quot;Docker-Desktop&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;docker-container&quot;&gt;Docker Container&lt;/h3&gt;
&lt;p&gt;A Docker Container is a running instance of a Docker image. Once an image starts running, it becomes a container. Containers are lightweight and isolated from the host operating system.&lt;/p&gt;
&lt;p&gt;You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start containers&lt;/li&gt;
&lt;li&gt;Stop containers&lt;/li&gt;
&lt;li&gt;Restart containers&lt;/li&gt;
&lt;li&gt;Delete containers&lt;/li&gt;
&lt;li&gt;Create multiple containers from one image&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts a new container using the Nginx image.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*867x1u6iKCdiVdQ-.png&quot; alt=&quot;Docker-container&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;dockerfile&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;p&gt;A Dockerfile is a text file containing instructions for building a Docker image. Instead of creating images manually, developers define the steps inside a Dockerfile.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM node:20

WORKDIR /app

COPY . .

RUN npm install

EXPOSE 3000

CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Docker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start from the Node.js image&lt;/li&gt;
&lt;li&gt;Create a working directory&lt;/li&gt;
&lt;li&gt;Copy project files&lt;/li&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;li&gt;Expose port 3000&lt;/li&gt;
&lt;li&gt;Start the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To build the image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build -t my-app .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;docker-hub&quot;&gt;Docker Hub&lt;/h3&gt;
&lt;p&gt;Docker Hub is an online registry where Docker images are stored and shared. It works similarly to GitHub, but instead of storing source code, it stores Docker images.&lt;/p&gt;
&lt;p&gt;You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Download official images&lt;/li&gt;
&lt;li&gt;Upload your own images&lt;/li&gt;
&lt;li&gt;Share images publicly or privately&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Popular images include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx, mysql, postgres, redis, node, ubuntu, python,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To download an image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker pull nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*ycT87QX0JrAO8zcm.png&quot; alt=&quot;Docker-Hub&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;docker-ports&quot;&gt;Docker Ports&lt;/h3&gt;
&lt;p&gt;Applications inside containers run in their own isolated environment. To access them from your computer, Docker maps container ports to ports on the host machine.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -p 8080:80 nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Port 80 is inside the container.&lt;/li&gt;
&lt;li&gt;Port 8080 is on your computer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Visiting:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will display the Nginx web page.&lt;/p&gt;
&lt;h3 id=&quot;docker-volumes-optional-but-useful-&quot;&gt;Docker Volumes (Optional but Useful)&lt;/h3&gt;
&lt;p&gt;Containers are temporary. If a container is deleted, its data is also deleted unless it is stored in a Docker Volume. Volumes allow data to persist.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker volume create mydata
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;installing-docker&quot;&gt;Installing Docker&lt;/h3&gt;
&lt;p&gt;The installation process depends on your operating system.&lt;/p&gt;
&lt;h3 id=&quot;windows&quot;&gt;Windows&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Download Desktop Docker&lt;/li&gt;
&lt;li&gt;Run the installer.&lt;/li&gt;
&lt;li&gt;Enable WSL 2 if prompted.&lt;/li&gt;
&lt;li&gt;Restart your computer.&lt;/li&gt;
&lt;li&gt;Open Docker Desktop.&lt;/li&gt;
&lt;li&gt;Wait until Docker starts successfully.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Verify installation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker --version or docker version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*aQsCnX7LYfpGybPS.png&quot; alt=&quot;Docker-Version&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;macos&quot;&gt;macOS&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Download Docker Desktop for macOS.&lt;/li&gt;
&lt;li&gt;Install the application.&lt;/li&gt;
&lt;li&gt;Launch Docker Desktop.&lt;/li&gt;
&lt;li&gt;Wait until Docker finishes starting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;p&gt;docker --version&lt;/p&gt;
&lt;h3 id=&quot;ubuntu-linux&quot;&gt;Ubuntu Linux&lt;/h3&gt;
&lt;p&gt;Update packages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install docker.io
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable docker
sudo systemctl start docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run without sudo (optional):&lt;/p&gt;
&lt;p&gt;sudo usermod -aG docker $USER&lt;br&gt;
Then log out and log back in.&lt;/p&gt;
&lt;h3 id=&quot;your-first-docker-deployment&quot;&gt;Your First Docker Deployment&lt;/h3&gt;
&lt;p&gt;Let’s deploy a simple static web landing page using Docker.&lt;/p&gt;
&lt;h3 id=&quot;step-1-search-the-docker-hub-for-an-image&quot;&gt;Step 1: search the docker hub for an image&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker pull nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker downloads the latest Nginx image from Docker Hub.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*sK34AhJ1XXUzrhgd.png&quot; alt=&quot;Docker-image&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-2-start-a-container&quot;&gt;Step 2: Start a Container&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker run -d -p 8088:80 --name welcome-to-docker docker/welcome-to-docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what each option means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-d runs the container in the background (detached mode).&lt;/li&gt;
&lt;li&gt;-p 8080:80 maps your computer's port 8080 to the container's port 80.&lt;/li&gt;
&lt;li&gt;--name welcome-to-docker gives the container an easy name to remember .&lt;/li&gt;
&lt;li&gt;docker/welcome-to-docker is the image used to create the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*lADqo8zJuXJwpR6S.png&quot; alt=&quot;docker/welcome-to-docker&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-3-verify-the-container-is-running&quot;&gt;Step 3: Verify the Container is Running&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see your running container listed.&lt;/p&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;h3 id=&quot;step-4-open-your-browser&quot;&gt;Step 4: Open Your Browser&lt;/h3&gt;
&lt;p&gt;Visit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:8088
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see the congratulation page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2333014123831853996&quot; alt=&quot;&quot;&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:828/format:webp/0*bL3ZSWheU3y3PFQS.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Congratulations you’ve deployed your first application using Docker!&lt;/p&gt;
&lt;p&gt;Step 5: Stop the Container&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker stop my-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-6-start-it-again&quot;&gt;Step 6: Start It Again&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker start my-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-7-remove-the-container&quot;&gt;Step 7: Remove the Container&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker rm my-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the container is still running, stop it first.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Through containerization, anyone can package applications and their dependencies into portable, lightweight containers that run consistently across different environments. With these fundamentals in place, you’re ready to explore more advanced Docker topics such as Docker Compose, custom image creation, networking, volumes, and multi-container applications.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28714/docker-for-beginners</guid>
<pubDate>Sat, 26 Sep 2026 11:43:18 +0000</pubDate>
</item>
<item>
<title>How It Works and Why We Use It in Real Projects</title>
<link>https://coderlegion.com/28163/how-it-works-and-why-we-use-it-in-real-projects</link>
<description>&lt;h2 id=&quot;docker-explained-how-it-works-and-why-we-use-it-in-real-projects&quot;&gt;Docker Explained: How It Works and Why We Use It in Real Projects&lt;/h2&gt;
&lt;p&gt;When I started working on backend and full-stack projects, one problem I faced was:&lt;/p&gt;
&lt;p&gt;“It works on my laptop, but why does it not work on another laptop?”&lt;/p&gt;
&lt;p&gt;Maybe the Node.js version is different.&lt;br&gt;
Maybe Python is not installed.&lt;br&gt;
Maybe MongoDB is configured differently.&lt;br&gt;
Maybe some dependency is missing.&lt;/p&gt;
&lt;p&gt;This is where I came across &lt;strong&gt;Docker&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Docker helps us package an application along with everything it needs to run, so we can run the same application in different environments.&lt;/p&gt;
&lt;p&gt;In this post, I’ll explain Docker in simple language, how it works, where we can use it in projects, and a real-world example.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-docker-&quot;&gt;What is Docker?&lt;/h3&gt;
&lt;p&gt;Docker is a platform that allows us to run applications inside &lt;strong&gt;containers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A container contains the application and the dependencies required to run it.&lt;/p&gt;
&lt;p&gt;For example, suppose I have a Node.js backend.&lt;/p&gt;
&lt;p&gt;My backend needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;npm packages&lt;/li&gt;
&lt;li&gt;Environment configuration&lt;/li&gt;
&lt;li&gt;Application code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of manually installing everything on another computer, I can create a Docker image containing these requirements.&lt;/p&gt;
&lt;p&gt;Then anyone can run that image using Docker.&lt;/p&gt;
&lt;p&gt;So, in simple words:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Docker makes the application environment consistent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The main idea is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My laptop → Docker → Same environment → Your laptop/server&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-do-we-need-docker-&quot;&gt;Why do we need Docker?&lt;/h2&gt;
&lt;p&gt;Let's take a simple example.&lt;/p&gt;
&lt;p&gt;Suppose I build a Node.js application using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js 20
MongoDB
Express.js
Mongoose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works perfectly on my laptop.&lt;/p&gt;
&lt;p&gt;Now I give the project to another developer.&lt;/p&gt;
&lt;p&gt;They might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js 18
Different MongoDB configuration
Different operating system
Different package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the application may not work properly.&lt;/p&gt;
&lt;p&gt;This creates the famous developer problem:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“But it works on my machine!” 😅&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Docker helps solve this problem by creating a similar environment wherever the application runs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-does-docker-actually-work-&quot;&gt;How does Docker actually work?&lt;/h2&gt;
&lt;p&gt;There are a few important terms we should understand.&lt;/p&gt;
&lt;h3 id=&quot;1-dockerfile&quot;&gt;1. Dockerfile&lt;/h3&gt;
&lt;p&gt;A Dockerfile is like a set of instructions for Docker.&lt;/p&gt;
&lt;p&gt;It tells Docker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which base environment to use&lt;/li&gt;
&lt;li&gt;Which dependencies to install&lt;/li&gt;
&lt;li&gt;Which files to copy&lt;/li&gt;
&lt;li&gt;Which command to run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a simple Node.js Dockerfile can look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's understand it step by step.&lt;/p&gt;
&lt;h4 id=&quot;from&quot;&gt;FROM&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Docker to start with a Node.js 20 environment.&lt;/p&gt;
&lt;h4 id=&quot;workdir&quot;&gt;WORKDIR&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;WORKDIR /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates/uses &lt;code&gt;/app&lt;/code&gt; as the working directory inside the container.&lt;/p&gt;
&lt;h4 id=&quot;copy&quot;&gt;COPY&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY package*.json ./
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This copies our package files into the container.&lt;/p&gt;
&lt;h4 id=&quot;run&quot;&gt;RUN&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;RUN npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This installs all the dependencies.&lt;/p&gt;
&lt;h4 id=&quot;copy-1&quot;&gt;COPY&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY . .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our application code is copied into the container.&lt;/p&gt;
&lt;h4 id=&quot;expose&quot;&gt;EXPOSE&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Docker that our application uses port 3000.&lt;/p&gt;
&lt;h4 id=&quot;cmd&quot;&gt;CMD&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the command that starts our application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-docker-image&quot;&gt;2. Docker Image&lt;/h2&gt;
&lt;p&gt;After creating the Dockerfile, we can build a &lt;strong&gt;Docker image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can think of an image as a &lt;strong&gt;blueprint/template&lt;/strong&gt; for our application.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
     ↓
Docker Image
     ↓
Docker Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image contains everything required to create a container.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-docker-container&quot;&gt;3. Docker Container&lt;/h2&gt;
&lt;p&gt;A container is the &lt;strong&gt;running instance of an image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js Application Image
           ↓
      Container 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also create multiple containers from the same image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;             Docker Image
             /     |     \
            ↓      ↓      ↓
       Container Container Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each container can run the same application in an isolated environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;image-vs-container&quot;&gt;Image vs Container&lt;/h2&gt;
&lt;p&gt;This was slightly confusing for me initially.&lt;/p&gt;
&lt;p&gt;A simple way to remember it is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Image = Blueprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Container = Running application created from that blueprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Think about a house.&lt;/p&gt;
&lt;p&gt;The building plan is like the &lt;strong&gt;Docker image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The actual house built from that plan is like the &lt;strong&gt;Docker container&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-happens-when-we-run-a-docker-container-&quot;&gt;What happens when we run a Docker container?&lt;/h2&gt;
&lt;p&gt;Suppose we run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker takes the &lt;code&gt;my-app&lt;/code&gt; image and creates a container from it.&lt;/p&gt;
&lt;p&gt;Then the application starts inside that container.&lt;/p&gt;
&lt;p&gt;The basic flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
     ↓
Build
     ↓
Docker Image
     ↓
Run
     ↓
Docker Container
     ↓
Application Running
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-architecture-1&quot;&gt;Docker Architecture&lt;/h2&gt;
&lt;p&gt;Docker mainly works using a client-server architecture.&lt;/p&gt;
&lt;p&gt;The important parts are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Docker Client
     ↓
Docker Daemon
     ↓
Docker Images
     ↓
Docker Containers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we run commands such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build
docker run
docker ps
docker stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the Docker client communicates with the Docker daemon.&lt;/p&gt;
&lt;p&gt;The daemon is responsible for creating and managing containers, images, networks, and other Docker resources.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-vs-virtual-machine&quot;&gt;Docker vs Virtual Machine&lt;/h2&gt;
&lt;p&gt;One common question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Is Docker the same as a Virtual Machine?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Both can isolate applications, but they work differently.&lt;/p&gt;
&lt;p&gt;A Virtual Machine usually includes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
Libraries
Guest OS
Virtual Hardware
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker containers share the host operating system's kernel, so they generally need fewer resources than full virtual machines.&lt;/p&gt;
&lt;p&gt;A simplified comparison:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Virtual Machine

Application
     ↓
Libraries
     ↓
Guest OS
     ↓
Hypervisor
     ↓
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
     ↓
Libraries
     ↓
Container
     ↓
Docker
     ↓
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because containers are lightweight, we can start them quickly and run many of them on the same machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;where-can-we-use-docker-in-a-project-&quot;&gt;Where can we use Docker in a project?&lt;/h2&gt;
&lt;p&gt;Docker becomes especially useful when our project has multiple services.&lt;/p&gt;
&lt;p&gt;For example, imagine a MERN application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React Frontend
       ↓
Node.js Backend
       ↓
MongoDB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of installing everything directly on a server, we can run different services in containers.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Docker
│
├── Frontend Container
│
├── Backend Container
│
└── Database Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a cleaner and more consistent development environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-compose&quot;&gt;Docker Compose&lt;/h2&gt;
&lt;p&gt;When our project has multiple containers, managing them individually can become difficult.&lt;/p&gt;
&lt;p&gt;For this, Docker Compose is useful.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Frontend
Backend
MongoDB
Redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can define these services in a &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;A simplified example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:

  backend:
    build: ./backend
    ports:
      - &quot;5000:5000&quot;

  frontend:
    build: ./frontend
    ports:
      - &quot;3000:3000&quot;

  mongodb:
    image: mongo
    ports:
      - &quot;27017:27017&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can start the whole application using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of manually starting every service.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-scenario&quot;&gt;Real-World Scenario&lt;/h2&gt;
&lt;p&gt;Let's take a real example.&lt;/p&gt;
&lt;p&gt;Imagine an e-commerce company.&lt;/p&gt;
&lt;p&gt;The application has:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Frontend
Backend API
Payment Service
Product Service
Database
Redis Cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without containers, setting up this environment on a new server can take a lot of time.&lt;/p&gt;
&lt;p&gt;Developers may need to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;MongoDB/PostgreSQL&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Different dependencies&lt;/li&gt;
&lt;li&gt;Correct versions&lt;/li&gt;
&lt;li&gt;Environment configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Docker, each service can have its own container.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                 Docker
                    │
       ┌────────────┼────────────┐
       ↓            ↓            ↓
   Frontend      Backend       Redis
   Container     Container     Container
                    │
                    ↓
               Database
               Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the development, testing, and deployment environments can be much more consistent.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-in-ci-cd&quot;&gt;Docker in CI/CD&lt;/h2&gt;
&lt;p&gt;Docker is also very useful in CI/CD.&lt;/p&gt;
&lt;p&gt;Suppose a developer pushes code to GitHub.&lt;/p&gt;
&lt;p&gt;A CI/CD pipeline can:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Developer
    ↓
Git Push
    ↓
GitHub
    ↓
CI/CD Pipeline
    ↓
Build Docker Image
    ↓
Run Tests
    ↓
Deploy
    ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means the same containerized application can move through different stages of development and deployment.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Development
      ↓
Testing
      ↓
Staging
      ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal is to reduce environment-related problems between these stages.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-in-my-own-projects&quot;&gt;Docker in My Own Projects&lt;/h2&gt;
&lt;p&gt;For the type of full-stack projects I work on, Docker can be useful when the project contains multiple technologies.&lt;/p&gt;
&lt;p&gt;For example, suppose I have a backend built with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js
Express.js
MongoDB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can use Docker to create a consistent environment for the backend and database.&lt;/p&gt;
&lt;p&gt;If later I deploy the project on a cloud server, I don't have to manually recreate the complete environment from scratch.&lt;/p&gt;
&lt;p&gt;I can build the image and run the container on the server.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;some-docker-commands-i-started-with&quot;&gt;Some Docker Commands I Started With&lt;/h2&gt;
&lt;p&gt;These are some basic commands that are useful when learning Docker.&lt;/p&gt;
&lt;p&gt;Check Docker version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build an image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-app .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See running containers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See all containers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker stop &amp;lt;container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker rm &amp;lt;container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See available images:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove an image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker rmi &amp;lt;image_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start Docker Compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop Docker Compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-problem-does-docker-actually-solve-&quot;&gt;What Problem Does Docker Actually Solve?&lt;/h2&gt;
&lt;p&gt;For me, the easiest way to understand Docker is not to remember definitions.&lt;/p&gt;
&lt;p&gt;Think about the problem first.&lt;/p&gt;
&lt;p&gt;Without Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;My application works on my computer.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But on another computer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Different OS
Different Node version
Missing dependency
Different database setup
Different configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
+
Dependencies
+
Required environment
        ↓
      Docker
        ↓
   Same environment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the main reason Docker is useful.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-is-not-only-for-deployment&quot;&gt;Docker is Not Only for Deployment&lt;/h2&gt;
&lt;p&gt;Initially, I thought Docker was mainly something used by DevOps engineers for deployment.&lt;/p&gt;
&lt;p&gt;But Docker can be useful during development too.&lt;/p&gt;
&lt;p&gt;It can help developers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up projects quickly&lt;/li&gt;
&lt;li&gt;Keep environments consistent&lt;/li&gt;
&lt;li&gt;Run databases locally&lt;/li&gt;
&lt;li&gt;Run multiple services&lt;/li&gt;
&lt;li&gt;Test applications in isolated environments&lt;/li&gt;
&lt;li&gt;Work with different versions of tools&lt;/li&gt;
&lt;li&gt;Prepare applications for deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So Docker is useful for both &lt;strong&gt;developers and DevOps teams&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-i-learned-from-docker&quot;&gt;What I Learned From Docker&lt;/h2&gt;
&lt;p&gt;The biggest thing I understood is that Docker is not just about running commands.&lt;/p&gt;
&lt;p&gt;The actual idea is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Package the application and its environment so it can run consistently in different places.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once I understood the difference between:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
    ↓
Image
    ↓
Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker became much easier to understand.&lt;/p&gt;
&lt;p&gt;And when we add Docker Compose, we can manage multiple containers together.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Docker is one of those technologies that looks complicated when you first see terms like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Dockerfile&lt;/li&gt;
&lt;li&gt;Volumes&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;Compose&lt;/li&gt;
&lt;li&gt;Docker Engine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the basic concept is actually simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker packages applications into containers so that they can run in a consistent and isolated environment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a beginner like me, I think the best way to learn Docker is not by memorizing all the commands.&lt;/p&gt;
&lt;p&gt;Start with one small project.&lt;/p&gt;
&lt;p&gt;Create a Dockerfile.&lt;/p&gt;
&lt;p&gt;Build an image.&lt;/p&gt;
&lt;p&gt;Run a container.&lt;/p&gt;
&lt;p&gt;Then try Docker Compose with a backend and database.&lt;/p&gt;
&lt;p&gt;Once you actually see the application running inside a container, the concept becomes much clearer.&lt;/p&gt;
&lt;p&gt;That's how I'm currently understanding Docker, and I think it's a useful technology to learn if you're working with &lt;strong&gt;Full Stack Development, Backend, Cloud, or DevOps.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28163/how-it-works-and-why-we-use-it-in-real-projects</guid>
<pubDate>Tue, 22 Sep 2026 14:48:32 +0000</pubDate>
</item>
<item>
<title>How It Works and Why We Use It in Real Projects</title>
<link>https://coderlegion.com/28162/how-it-works-and-why-we-use-it-in-real-projects</link>
<description>&lt;h2 id=&quot;docker-explained-how-it-works-and-why-we-use-it-in-real-projects-1&quot;&gt;Docker Explained: How It Works and Why We Use It in Real Projects&lt;/h2&gt;
&lt;p&gt;When I started working on backend and full-stack projects, one problem I faced was:&lt;/p&gt;
&lt;p&gt;“It works on my laptop, but why does it not work on another laptop?”&lt;/p&gt;
&lt;p&gt;Maybe the Node.js version is different.&lt;br&gt;
Maybe Python is not installed.&lt;br&gt;
Maybe MongoDB is configured differently.&lt;br&gt;
Maybe some dependency is missing.&lt;/p&gt;
&lt;p&gt;This is where I came across &lt;strong&gt;Docker&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Docker helps us package an application along with everything it needs to run, so we can run the same application in different environments.&lt;/p&gt;
&lt;p&gt;In this post, I’ll explain Docker in simple language, how it works, where we can use it in projects, and a real-world example.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-docker--1&quot;&gt;What is Docker?&lt;/h3&gt;
&lt;p&gt;Docker is a platform that allows us to run applications inside &lt;strong&gt;containers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A container contains the application and the dependencies required to run it.&lt;/p&gt;
&lt;p&gt;For example, suppose I have a Node.js backend.&lt;/p&gt;
&lt;p&gt;My backend needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;npm packages&lt;/li&gt;
&lt;li&gt;Environment configuration&lt;/li&gt;
&lt;li&gt;Application code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of manually installing everything on another computer, I can create a Docker image containing these requirements.&lt;/p&gt;
&lt;p&gt;Then anyone can run that image using Docker.&lt;/p&gt;
&lt;p&gt;So, in simple words:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Docker makes the application environment consistent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The main idea is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My laptop → Docker → Same environment → Your laptop/server&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-do-we-need-docker--1&quot;&gt;Why do we need Docker?&lt;/h2&gt;
&lt;p&gt;Let's take a simple example.&lt;/p&gt;
&lt;p&gt;Suppose I build a Node.js application using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js 20
MongoDB
Express.js
Mongoose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works perfectly on my laptop.&lt;/p&gt;
&lt;p&gt;Now I give the project to another developer.&lt;/p&gt;
&lt;p&gt;They might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js 18
Different MongoDB configuration
Different operating system
Different package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the application may not work properly.&lt;/p&gt;
&lt;p&gt;This creates the famous developer problem:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“But it works on my machine!” 😅&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Docker helps solve this problem by creating a similar environment wherever the application runs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-does-docker-actually-work--1&quot;&gt;How does Docker actually work?&lt;/h2&gt;
&lt;p&gt;There are a few important terms we should understand.&lt;/p&gt;
&lt;h3 id=&quot;1-dockerfile-1&quot;&gt;1. Dockerfile&lt;/h3&gt;
&lt;p&gt;A Dockerfile is like a set of instructions for Docker.&lt;/p&gt;
&lt;p&gt;It tells Docker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which base environment to use&lt;/li&gt;
&lt;li&gt;Which dependencies to install&lt;/li&gt;
&lt;li&gt;Which files to copy&lt;/li&gt;
&lt;li&gt;Which command to run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a simple Node.js Dockerfile can look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's understand it step by step.&lt;/p&gt;
&lt;h4 id=&quot;from-1&quot;&gt;FROM&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Docker to start with a Node.js 20 environment.&lt;/p&gt;
&lt;h4 id=&quot;workdir-1&quot;&gt;WORKDIR&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;WORKDIR /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates/uses &lt;code&gt;/app&lt;/code&gt; as the working directory inside the container.&lt;/p&gt;
&lt;h4 id=&quot;copy-2&quot;&gt;COPY&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY package*.json ./
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This copies our package files into the container.&lt;/p&gt;
&lt;h4 id=&quot;run-1&quot;&gt;RUN&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;RUN npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This installs all the dependencies.&lt;/p&gt;
&lt;h4 id=&quot;copy-3&quot;&gt;COPY&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY . .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our application code is copied into the container.&lt;/p&gt;
&lt;h4 id=&quot;expose-1&quot;&gt;EXPOSE&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Docker that our application uses port 3000.&lt;/p&gt;
&lt;h4 id=&quot;cmd-1&quot;&gt;CMD&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the command that starts our application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-docker-image-1&quot;&gt;2. Docker Image&lt;/h2&gt;
&lt;p&gt;After creating the Dockerfile, we can build a &lt;strong&gt;Docker image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can think of an image as a &lt;strong&gt;blueprint/template&lt;/strong&gt; for our application.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
     ↓
Docker Image
     ↓
Docker Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image contains everything required to create a container.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-docker-container-1&quot;&gt;3. Docker Container&lt;/h2&gt;
&lt;p&gt;A container is the &lt;strong&gt;running instance of an image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js Application Image
           ↓
      Container 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also create multiple containers from the same image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;             Docker Image
             /     |     \
            ↓      ↓      ↓
       Container Container Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each container can run the same application in an isolated environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;image-vs-container-1&quot;&gt;Image vs Container&lt;/h2&gt;
&lt;p&gt;This was slightly confusing for me initially.&lt;/p&gt;
&lt;p&gt;A simple way to remember it is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Image = Blueprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Container = Running application created from that blueprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Think about a house.&lt;/p&gt;
&lt;p&gt;The building plan is like the &lt;strong&gt;Docker image&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The actual house built from that plan is like the &lt;strong&gt;Docker container&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-happens-when-we-run-a-docker-container--1&quot;&gt;What happens when we run a Docker container?&lt;/h2&gt;
&lt;p&gt;Suppose we run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker takes the &lt;code&gt;my-app&lt;/code&gt; image and creates a container from it.&lt;/p&gt;
&lt;p&gt;Then the application starts inside that container.&lt;/p&gt;
&lt;p&gt;The basic flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
     ↓
Build
     ↓
Docker Image
     ↓
Run
     ↓
Docker Container
     ↓
Application Running
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-architecture-2&quot;&gt;Docker Architecture&lt;/h2&gt;
&lt;p&gt;Docker mainly works using a client-server architecture.&lt;/p&gt;
&lt;p&gt;The important parts are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Docker Client
     ↓
Docker Daemon
     ↓
Docker Images
     ↓
Docker Containers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we run commands such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build
docker run
docker ps
docker stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the Docker client communicates with the Docker daemon.&lt;/p&gt;
&lt;p&gt;The daemon is responsible for creating and managing containers, images, networks, and other Docker resources.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-vs-virtual-machine-1&quot;&gt;Docker vs Virtual Machine&lt;/h2&gt;
&lt;p&gt;One common question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Is Docker the same as a Virtual Machine?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Both can isolate applications, but they work differently.&lt;/p&gt;
&lt;p&gt;A Virtual Machine usually includes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
Libraries
Guest OS
Virtual Hardware
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker containers share the host operating system's kernel, so they generally need fewer resources than full virtual machines.&lt;/p&gt;
&lt;p&gt;A simplified comparison:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Virtual Machine

Application
     ↓
Libraries
     ↓
Guest OS
     ↓
Hypervisor
     ↓
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
     ↓
Libraries
     ↓
Container
     ↓
Docker
     ↓
Host OS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because containers are lightweight, we can start them quickly and run many of them on the same machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;where-can-we-use-docker-in-a-project--1&quot;&gt;Where can we use Docker in a project?&lt;/h2&gt;
&lt;p&gt;Docker becomes especially useful when our project has multiple services.&lt;/p&gt;
&lt;p&gt;For example, imagine a MERN application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React Frontend
       ↓
Node.js Backend
       ↓
MongoDB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of installing everything directly on a server, we can run different services in containers.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Docker
│
├── Frontend Container
│
├── Backend Container
│
└── Database Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a cleaner and more consistent development environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-compose-1&quot;&gt;Docker Compose&lt;/h2&gt;
&lt;p&gt;When our project has multiple containers, managing them individually can become difficult.&lt;/p&gt;
&lt;p&gt;For this, Docker Compose is useful.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Frontend
Backend
MongoDB
Redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can define these services in a &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;A simplified example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:

  backend:
    build: ./backend
    ports:
      - &quot;5000:5000&quot;

  frontend:
    build: ./frontend
    ports:
      - &quot;3000:3000&quot;

  mongodb:
    image: mongo
    ports:
      - &quot;27017:27017&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can start the whole application using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of manually starting every service.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-scenario-1&quot;&gt;Real-World Scenario&lt;/h2&gt;
&lt;p&gt;Let's take a real example.&lt;/p&gt;
&lt;p&gt;Imagine an e-commerce company.&lt;/p&gt;
&lt;p&gt;The application has:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Frontend
Backend API
Payment Service
Product Service
Database
Redis Cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without containers, setting up this environment on a new server can take a lot of time.&lt;/p&gt;
&lt;p&gt;Developers may need to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;MongoDB/PostgreSQL&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Different dependencies&lt;/li&gt;
&lt;li&gt;Correct versions&lt;/li&gt;
&lt;li&gt;Environment configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Docker, each service can have its own container.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                 Docker
                    │
       ┌────────────┼────────────┐
       ↓            ↓            ↓
   Frontend      Backend       Redis
   Container     Container     Container
                    │
                    ↓
               Database
               Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the development, testing, and deployment environments can be much more consistent.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-in-ci-cd-1&quot;&gt;Docker in CI/CD&lt;/h2&gt;
&lt;p&gt;Docker is also very useful in CI/CD.&lt;/p&gt;
&lt;p&gt;Suppose a developer pushes code to GitHub.&lt;/p&gt;
&lt;p&gt;A CI/CD pipeline can:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Developer
    ↓
Git Push
    ↓
GitHub
    ↓
CI/CD Pipeline
    ↓
Build Docker Image
    ↓
Run Tests
    ↓
Deploy
    ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means the same containerized application can move through different stages of development and deployment.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Development
      ↓
Testing
      ↓
Staging
      ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal is to reduce environment-related problems between these stages.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-in-my-own-projects-1&quot;&gt;Docker in My Own Projects&lt;/h2&gt;
&lt;p&gt;For the type of full-stack projects I work on, Docker can be useful when the project contains multiple technologies.&lt;/p&gt;
&lt;p&gt;For example, suppose I have a backend built with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Node.js
Express.js
MongoDB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can use Docker to create a consistent environment for the backend and database.&lt;/p&gt;
&lt;p&gt;If later I deploy the project on a cloud server, I don't have to manually recreate the complete environment from scratch.&lt;/p&gt;
&lt;p&gt;I can build the image and run the container on the server.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;some-docker-commands-i-started-with-1&quot;&gt;Some Docker Commands I Started With&lt;/h2&gt;
&lt;p&gt;These are some basic commands that are useful when learning Docker.&lt;/p&gt;
&lt;p&gt;Check Docker version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build an image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-app .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See running containers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See all containers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps -a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker stop &amp;lt;container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove a container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker rm &amp;lt;container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See available images:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove an image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker rmi &amp;lt;image_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start Docker Compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop Docker Compose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-problem-does-docker-actually-solve--1&quot;&gt;What Problem Does Docker Actually Solve?&lt;/h2&gt;
&lt;p&gt;For me, the easiest way to understand Docker is not to remember definitions.&lt;/p&gt;
&lt;p&gt;Think about the problem first.&lt;/p&gt;
&lt;p&gt;Without Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;My application works on my computer.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But on another computer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Different OS
Different Node version
Missing dependency
Different database setup
Different configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application
+
Dependencies
+
Required environment
        ↓
      Docker
        ↓
   Same environment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the main reason Docker is useful.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-is-not-only-for-deployment-1&quot;&gt;Docker is Not Only for Deployment&lt;/h2&gt;
&lt;p&gt;Initially, I thought Docker was mainly something used by DevOps engineers for deployment.&lt;/p&gt;
&lt;p&gt;But Docker can be useful during development too.&lt;/p&gt;
&lt;p&gt;It can help developers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up projects quickly&lt;/li&gt;
&lt;li&gt;Keep environments consistent&lt;/li&gt;
&lt;li&gt;Run databases locally&lt;/li&gt;
&lt;li&gt;Run multiple services&lt;/li&gt;
&lt;li&gt;Test applications in isolated environments&lt;/li&gt;
&lt;li&gt;Work with different versions of tools&lt;/li&gt;
&lt;li&gt;Prepare applications for deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So Docker is useful for both &lt;strong&gt;developers and DevOps teams&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-i-learned-from-docker-1&quot;&gt;What I Learned From Docker&lt;/h2&gt;
&lt;p&gt;The biggest thing I understood is that Docker is not just about running commands.&lt;/p&gt;
&lt;p&gt;The actual idea is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Package the application and its environment so it can run consistently in different places.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once I understood the difference between:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Dockerfile
    ↓
Image
    ↓
Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker became much easier to understand.&lt;/p&gt;
&lt;p&gt;And when we add Docker Compose, we can manage multiple containers together.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-2&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Docker is one of those technologies that looks complicated when you first see terms like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Dockerfile&lt;/li&gt;
&lt;li&gt;Volumes&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;Compose&lt;/li&gt;
&lt;li&gt;Docker Engine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the basic concept is actually simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker packages applications into containers so that they can run in a consistent and isolated environment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a beginner like me, I think the best way to learn Docker is not by memorizing all the commands.&lt;/p&gt;
&lt;p&gt;Start with one small project.&lt;/p&gt;
&lt;p&gt;Create a Dockerfile.&lt;/p&gt;
&lt;p&gt;Build an image.&lt;/p&gt;
&lt;p&gt;Run a container.&lt;/p&gt;
&lt;p&gt;Then try Docker Compose with a backend and database.&lt;/p&gt;
&lt;p&gt;Once you actually see the application running inside a container, the concept becomes much clearer.&lt;/p&gt;
&lt;p&gt;That's how I'm currently understanding Docker, and I think it's a useful technology to learn if you're working with &lt;strong&gt;Full Stack Development, Backend, Cloud, or DevOps.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28162/how-it-works-and-why-we-use-it-in-real-projects</guid>
<pubDate>Tue, 22 Sep 2026 13:56:16 +0000</pubDate>
</item>
<item>
<title>SafeLine WAF Docker Compose Example</title>
<link>https://coderlegion.com/28091/safeline-waf-docker-compose-example</link>
<description>&lt;h2 id=&quot;safeline-waf-docker-compose-example&quot;&gt;SafeLine WAF Docker Compose Example&lt;/h2&gt;
&lt;p&gt;SafeLine ships as a set of containers, and Docker Compose is the simplest way to bring up the whole stack — management console, detection engine, and the supporting database and cache — on a single host.&lt;/p&gt;
&lt;h3 id=&quot;the-fastest-path&quot;&gt;The fastest path&lt;/h3&gt;
&lt;p&gt;The official one-line installer handles the compose file for you:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It writes a &lt;code&gt;docker-compose.yml&lt;/code&gt; in the SafeLine directory and starts the services. After it finishes, open the console at &lt;code&gt;https://&amp;lt;host-ip&amp;gt;:9443&lt;/code&gt; to add your protected sites.&lt;/p&gt;
&lt;h3 id=&quot;what-the-stack-contains&quot;&gt;What the stack contains&lt;/h3&gt;
&lt;p&gt;A typical SafeLine compose defines a few services working together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Management console&lt;/strong&gt; — the web UI on &lt;code&gt;:9443&lt;/code&gt; where you configure sites and view logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detection engine / proxy&lt;/strong&gt; — the component that actually inspects and forwards traffic on &lt;code&gt;:80&lt;/code&gt; / &lt;code&gt;:443&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database&lt;/strong&gt; — stores configuration and records.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache&lt;/strong&gt; — used for session and challenge state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An illustrative compose looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  safeline-mgt:            # management console
    image: chaitin/safeline-mgt:latest
    ports: [&quot;9443:9443&quot;]
  safeline-detector:       # inspection + reverse proxy
    image: chaitin/safeline-detector:latest
    ports: [&quot;80:80&quot;, &quot;443:443&quot;]
  safeline-pg:             # database
    image: chaitin/safeline-pg:latest
  safeline-redis:          # cache
    image: chaitin/safeline-redis:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Treat the exact image names, tags, and ports above as illustrative — pull the authoritative &lt;code&gt;docker-compose.yml&lt;/code&gt; from the official installer or docs, since these can change between versions.&lt;/p&gt;
&lt;h3 id=&quot;customizing&quot;&gt;Customizing&lt;/h3&gt;
&lt;p&gt;Once it's up, common tweaks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persist data&lt;/strong&gt; — add named volumes for the database and config so they survive container recreation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change ports&lt;/strong&gt; — if &lt;code&gt;:80&lt;/code&gt;/&lt;code&gt;:443&lt;/code&gt; are taken, map the proxy to different host ports and adjust your upstream/DNS accordingly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protect a site&lt;/strong&gt; — in the console, add a site whose upstream is your app (e.g. &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;faq-1&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;do-i-need-to-write-the-compose-myself-&quot;&gt;Do I need to write the compose myself?&lt;/h4&gt;
&lt;p&gt;No — the installer generates it. Hand-writing is only if you want a custom layout.&lt;/p&gt;
&lt;h4 id=&quot;can-i-run-it-alongside-other-compose-stacks-&quot;&gt;Can I run it alongside other compose stacks?&lt;/h4&gt;
&lt;p&gt;Yes; just keep the ports and volume names from colliding with other services.&lt;/p&gt;
&lt;h4 id=&quot;free-tier-&quot;&gt;Free tier?&lt;/h4&gt;
&lt;p&gt;The Community Edition covers 10 apps at 800 QPS for free.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;That's it — a single compose stack gives you a running self-hosted WAF.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⭐ &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;SafeLine WAF on GitHub&lt;/a&gt; — give it a star if you find it useful&lt;/li&gt;
&lt;li&gt;🔗 &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;Official Docs&lt;/a&gt; — installation guide, configuration, and API reference&lt;/li&gt;
&lt;li&gt;🧪 &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;Live Demo&lt;/a&gt; — see the dashboard in action (no login required)&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28091/safeline-waf-docker-compose-example</guid>
<pubDate>Tue, 22 Sep 2026 03:23:27 +0000</pubDate>
</item>
<item>
<title>How to Install SafeLine WAF with Docker in 10 Minutes</title>
<link>https://coderlegion.com/27449/how-to-install-safeline-waf-with-docker-in-10-minutes</link>
<description>&lt;h2 id=&quot;how-to-install-safeline-waf-with-docker-in-10-minutes&quot;&gt;How to Install SafeLine WAF with Docker in 10 Minutes&lt;/h2&gt;
&lt;p&gt;SafeLine is designed to run as a container, which makes Docker the natural&lt;br&gt;
deployment path. This walkthrough gets you from zero to a working reverse-proxy WAF.&lt;/p&gt;
&lt;h3 id=&quot;what-you-need&quot;&gt;What you need&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A Linux host (VM or bare metal) with Docker installed.&lt;/li&gt;
&lt;li&gt;At least 1 CPU, 1 GB RAM, 5 GB free disk.&lt;/li&gt;
&lt;li&gt;A service you want to protect (e.g., a web app on port 8080).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-1-install-safeline&quot;&gt;Step 1: Install SafeLine&lt;/h3&gt;
&lt;p&gt;Run the official installer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pulls the SafeLine containers and starts the management console.&lt;/p&gt;
&lt;h3 id=&quot;step-2-open-the-dashboard&quot;&gt;Step 2: Open the dashboard&lt;/h3&gt;
&lt;p&gt;Navigate to &lt;code&gt;https://&amp;lt;your-host-ip&amp;gt;:9443&lt;/code&gt;. Complete the initial setup and create an admin&lt;br&gt;
account. You can also explore the live demo first at&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-3-add-your-upstream&quot;&gt;Step 3: Add your upstream&lt;/h3&gt;
&lt;p&gt;In the console, register the site you want to protect and point its upstream at your real app&lt;br&gt;
(e.g., &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt;). SafeLine now inspects traffic before proxying it.&lt;/p&gt;
&lt;h3 id=&quot;step-4-route-traffic&quot;&gt;Step 4: Route traffic&lt;/h3&gt;
&lt;p&gt;Point your domain or load balancer at the SafeLine listener instead of the app directly. SafeLine&lt;br&gt;
blocks malicious requests (SQLi, XSS, bot abuse) using its semantic-analysis engine.&lt;/p&gt;
&lt;h3 id=&quot;faq-2&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;do-i-need-docker-compose-&quot;&gt;Do I need docker-compose?&lt;/h4&gt;
&lt;p&gt;The installer manages containers for you; compose is optional for custom setups.&lt;/p&gt;
&lt;h4 id=&quot;what-if-the-container-restarts-&quot;&gt;What if the container restarts?&lt;/h4&gt;
&lt;p&gt;SafeLine persists config; set a restart policy so it comes back automatically.&lt;/p&gt;
&lt;h4 id=&quot;is-this-production-ready-&quot;&gt;Is this production-ready?&lt;/h4&gt;
&lt;p&gt;Yes — the Community Edition is free and handles 10 apps at 800 QPS.&lt;/p&gt;
&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h3&gt;
&lt;p&gt;If you want a &lt;strong&gt;self-hosted WAF&lt;/strong&gt; you fully control, SafeLine is worth a look. It sits in&lt;br&gt;
front of your existing stack as a container, uses a semantic-analysis engine to catch SQL&lt;br&gt;
injection, XSS, and bot abuse with very low false positives, and the &lt;strong&gt;Community Edition is&lt;br&gt;
free&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Official docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://demo.waf.chaitin.com:9443/statistics&quot;&gt;https://demo.waf.chaitin.com:9443/statistics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deploy it in minutes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &quot;$(curl -fsSLk https://waf.chaitin.com/release/latest/manager.sh)&quot; -- --en
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27449/how-to-install-safeline-waf-with-docker-in-10-minutes</guid>
<pubDate>Thu, 17 Sep 2026 04:16:13 +0000</pubDate>
</item>
<item>
<title>Running Qwen Locally as a Third-Party Backend for Claude: What Actually Broke</title>
<link>https://coderlegion.com/27228/running-qwen-locally-as-a-third-party-backend-for-claude-what-actually-broke</link>
<description>&lt;p&gt;I didn't set out to write a tutorial. This is closer to a log of what happened when I tried to wire a locally-hosted Qwen model into Claude's third-party inference gateway, and the things that quietly broke before it worked.&lt;/p&gt;
&lt;h3 id=&quot;why-bother-with-a-local-backend-at-all&quot;&gt;Why bother with a local backend at all&lt;/h3&gt;
&lt;p&gt;Most of my work involves research and document generation that doesn't need to hit a hosted API every single time. I wanted something I could run on my own machine, mostly to experiment freely without worrying about racking up API calls for every small test. The idea wasn't to replace Claude, but to have a local option sitting behind the same interface, one I could point at when the task didn't call for a hosted model.&lt;/p&gt;
&lt;p&gt;Qwen3.6 looked like a reasonable fit for this. Not because it's the newest or the most talked about, but because it's the kind of model you can actually run on a single machine without needing a rack of GPUs.&lt;/p&gt;
&lt;h3 id=&quot;the-basic-shape-of-the-setup&quot;&gt;The basic shape of the setup&lt;/h3&gt;
&lt;p&gt;The architecture ended up being fairly simple on paper: &lt;code&gt;llama-server&lt;/code&gt; runs the model locally, and &lt;code&gt;LiteLLM&lt;/code&gt; sits in front of it as a proxy that speaks the API shape Claude's third-party inference gateway expects. LiteLLM runs inside a Docker container (via Colima on macOS), and llama-server runs directly on the host.&lt;/p&gt;
&lt;p&gt;On paper, that's three components talking to each other. In practice, it took a bit of trial and error before they actually did.&lt;/p&gt;
&lt;h3 id=&quot;the-docker-networking-trap&quot;&gt;The Docker networking trap&lt;/h3&gt;
&lt;p&gt;The first real snag came from something that seems obvious in hindsight: LiteLLM, running inside a Docker container, tried to reach llama-server via &lt;code&gt;127.0.0.1&lt;/code&gt;. That address, from inside a container, doesn't point to the host machine. It points to the container's own loopback interface, which has nothing running on it.&lt;/p&gt;
&lt;p&gt;The fix was switching to &lt;code&gt;host.docker.internal&lt;/code&gt; instead. It's a small detail, but it's the kind of thing that costs an hour of confused debugging if you haven't hit it before. If you're bridging a host-level service to something running inside Docker, this is worth checking first, before assuming the service itself is broken.&lt;/p&gt;
&lt;h3 id=&quot;hitting-a-context-window-wall&quot;&gt;Hitting a context window wall&lt;/h3&gt;
&lt;p&gt;Once the plumbing was working, a different problem showed up during a longer conversation: a &lt;code&gt;ContextWindowExceededError&lt;/code&gt;, with the context window set to 32768 tokens on llama-server. It's a reminder that a locally-served model doesn't quietly resize itself the way a hosted API sometimes appears to. The ceiling is whatever you set at launch, and it will tell you when you've hit it.&lt;/p&gt;
&lt;h3 id=&quot;the-trade-off-nobody-tells-you-about-upfront&quot;&gt;The trade-off nobody tells you about upfront&lt;/h3&gt;
&lt;p&gt;I started with Qwen3.6-27B, then moved to Qwen3.6-35B-A3B. The larger model scores well (Qwen3.6-27B benchmarks around 77.2% on SWE-bench), but it's also slower to respond. That trade-off between quality and speed is easy to state in the abstract, but it feels very different once you're the one waiting on time-to-first-token during an actual working session.&lt;/p&gt;
&lt;p&gt;There's no universally correct answer here. It depends on whether the task in front of you needs the better score more than it needs the faster reply.&lt;/p&gt;
&lt;h3 id=&quot;where-things-stand-now&quot;&gt;Where things stand now&lt;/h3&gt;
&lt;p&gt;LiteLLM is successfully connected to Claude's third-party inference gateway, with the local model mapped in the gateway configuration. It works. It is also, honestly, still a work in progress rather than a finished setup I'd call done. Configuration around slot persistence, context sizing, and model choice is something I expect to keep adjusting as I use it more.&lt;/p&gt;
&lt;p&gt;If you've built something similar, or hit a different wall along the way, I'd be glad to compare notes. This kind of setup seems to have more undocumented edges than the clean tutorials suggest.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27228/running-qwen-locally-as-a-third-party-backend-for-claude-what-actually-broke</guid>
<pubDate>Tue, 15 Sep 2026 06:21:26 +0000</pubDate>
</item>
<item>
<title>How I Connected a Dockerized Python API to a Local Mistral Model</title>
<link>https://coderlegion.com/26086/how-i-connected-a-dockerized-python-api-to-a-local-mistral-model</link>
<description>&lt;h2 id=&quot;how-i-connected-a-dockerized-python-api-to-a-local-mistral-model&quot;&gt;How I Connected a Dockerized Python API to a Local Mistral Model&lt;/h2&gt;
&lt;p&gt;I’m currently building the AI foundation for &lt;strong&gt;MyZubster&lt;/strong&gt;, and one of my first goals was to create a local development environment that could run without relying on paid external AI APIs.&lt;/p&gt;
&lt;p&gt;The stack uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python and Flask for the application API&lt;/li&gt;
&lt;li&gt;Docker Compose for orchestration&lt;/li&gt;
&lt;li&gt;Ollama for local model inference&lt;/li&gt;
&lt;li&gt;Mistral as the language model&lt;/li&gt;
&lt;li&gt;Qdrant for vector storage&lt;/li&gt;
&lt;li&gt;Open WebUI for manual testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post explains how the services are connected and highlights a few issues I encountered along the way.&lt;/p&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The architecture&lt;/h3&gt;
&lt;p&gt;The planned AI workflow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User request
    ↓
MyZubster Flask API
    ↓
Semantic search in Qdrant
    ↓
Relevant MyZubster data
    ↓
Mistral through Ollama
    ↓
Generated response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qdrant will provide the retrieval part of the RAG pipeline, while Mistral will generate an answer using the retrieved context.&lt;/p&gt;
&lt;h3 id=&quot;why-ollama-runs-on-the-host&quot;&gt;Why Ollama runs on the host&lt;/h3&gt;
&lt;p&gt;Ollama was already installed and running directly on Windows at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;mistral:latest&lt;/code&gt; model was also already downloaded.&lt;/p&gt;
&lt;p&gt;I decided not to add another Ollama container because that would duplicate model storage, increase memory usage and conflict with the port already occupied by the host process.&lt;/p&gt;
&lt;p&gt;Docker Desktop provides the special hostname:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;host.docker.internal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Containers can use it to connect to services running on the host machine.&lt;/p&gt;
&lt;p&gt;Inside the MyZubster API container, the Ollama address therefore becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://host.docker.internal:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;verifying-the-connection&quot;&gt;Verifying the connection&lt;/h3&gt;
&lt;p&gt;Before changing the application, I tested the connection using a temporary curl container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker run --rm curlimages/curl:8.12.1 `
  http://host.docker.internal:11434/api/tags
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ollama returned the installed model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;models&quot;: [
    {
      &quot;name&quot;: &quot;mistral:latest&quot;,
      &quot;parameter_size&quot;: &quot;7.2B&quot;,
      &quot;quantization_level&quot;: &quot;Q4_K_M&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that Docker could communicate with Ollama on the Windows host.&lt;/p&gt;
&lt;h3 id=&quot;docker-compose-stack&quot;&gt;Docker Compose stack&lt;/h3&gt;
&lt;p&gt;Here is the Compose configuration used for the local environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  api:
    build:
      context: .
    init: true
    ports:
      - &quot;5000:5000&quot;
    environment:
      MYZUBSTER_HOST: &quot;0.0.0.0&quot;
      MYZUBSTER_PORT: &quot;5000&quot;
      MYZUBSTER_OBSERVATIONS_FILE: &quot;/data/observations.json&quot;
      OLLAMA_BASE_URL: &quot;http://host.docker.internal:11434&quot;
      OLLAMA_MODEL: &quot;mistral:latest&quot;
      QDRANT_URL: &quot;http://qdrant:6333&quot;
      QDRANT_COLLECTION: &quot;myzubster&quot;
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    volumes:
      - observations-data:/data
    depends_on:
      qdrant:
        condition: service_started
    restart: unless-stopped

  qdrant:
    image: qdrant/qdrant:latest
    ports:
      - &quot;127.0.0.1:6333:6333&quot;
      - &quot;127.0.0.1:6334:6334&quot;
    volumes:
      - qdrant-data:/qdrant/storage
    restart: unless-stopped

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    ports:
      - &quot;127.0.0.1:3000:8080&quot;
    environment:
      OLLAMA_BASE_URL: &quot;http://host.docker.internal:11434&quot;
      WEBUI_AUTH: &quot;true&quot;
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    volumes:
      - open-webui-data:/app/backend/data
    restart: unless-stopped

volumes:
  observations-data:
  qdrant-data:
  open-webui-data:

networks:
  default:
    name: myzubster-ai-network
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API communicates with Qdrant using its Compose service name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://qdrant:6333
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It communicates with Ollama through the host gateway:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://host.docker.internal:11434
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;starting-the-environment&quot;&gt;Starting the environment&lt;/h3&gt;
&lt;p&gt;I first validate the Compose file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I pull the external images and start the stack:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose pull
docker compose down
docker compose up -d --build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service status can be checked with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful logs are available through:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose logs --tail 100 api
docker compose logs --tail 100 qdrant
docker compose logs --tail 100 open-webui
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;testing-ollama-from-the-application-container&quot;&gt;Testing Ollama from the application container&lt;/h3&gt;
&lt;p&gt;A successful request from Windows does not necessarily prove that the application container can reach Ollama.&lt;/p&gt;
&lt;p&gt;I tested the full connection from inside the API container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose exec api python -c &quot;import json,os,urllib.request; url=os.environ['OLLAMA_BASE_URL']+'/api/generate'; data=json.dumps({'model':os.environ['OLLAMA_MODEL'],'prompt':'Reply only with: MyZubster connected','stream':False}).encode(); req=urllib.request.Request(url,data=data,headers={'Content-Type':'application/json'}); result=json.loads(urllib.request.urlopen(req,timeout=120).read()); print(result['response'].strip())&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The container received a generated response, confirming that the networking and environment variables were configured correctly.&lt;/p&gt;
&lt;h3 id=&quot;a-powershell-trap&quot;&gt;A PowerShell trap&lt;/h3&gt;
&lt;p&gt;One mistake I made was pasting YAML directly into PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  api:
    build:
      context: .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PowerShell interpreted each YAML key as a command and returned errors such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;The term 'services:' is not recognized as the name of a cmdlet
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Compose configuration must be saved as &lt;code&gt;docker-compose.yml&lt;/code&gt;; it cannot be executed directly.&lt;/p&gt;
&lt;p&gt;To open the file in Visual Studio Code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;code docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always validate it afterward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;available-services&quot;&gt;Available services&lt;/h3&gt;
&lt;p&gt;After startup, the local tools are available at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MyZubster API: &lt;code&gt;http://localhost:5000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open WebUI: &lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Qdrant dashboard: &lt;code&gt;http://localhost:6333/dashboard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ollama API: &lt;code&gt;http://localhost:11434&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qdrant and Open WebUI are bound to &lt;code&gt;127.0.0.1&lt;/code&gt;, so their ports are not exposed to other devices on the network.&lt;/p&gt;
&lt;h3 id=&quot;what-comes-next&quot;&gt;What comes next&lt;/h3&gt;
&lt;p&gt;The containers provide the infrastructure, but the actual RAG pipeline still needs to be implemented.&lt;/p&gt;
&lt;p&gt;The next development steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install an embedding model in Ollama.&lt;/li&gt;
&lt;li&gt;Create a Qdrant collection with the correct vector dimensions.&lt;/li&gt;
&lt;li&gt;Generate embeddings for MyZubster content.&lt;/li&gt;
&lt;li&gt;Store vectors together with useful metadata.&lt;/li&gt;
&lt;li&gt;Embed incoming user questions.&lt;/li&gt;
&lt;li&gt;Retrieve the most relevant records from Qdrant.&lt;/li&gt;
&lt;li&gt;Add the retrieved context to the Mistral prompt.&lt;/li&gt;
&lt;li&gt;Expose the workflow through an API endpoint.&lt;/li&gt;
&lt;li&gt;Add integration tests and response validation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One possible endpoint will be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /api/ai/ask
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;question&quot;: &quot;Which products use sustainable packaging?&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application will search its own indexed data and ask Mistral to generate an answer grounded in those results.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-3&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;This setup creates a practical local AI development environment while keeping data and model inference on the developer’s machine.&lt;/p&gt;
&lt;p&gt;The most important lesson was to verify each connection independently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Host to Ollama&lt;/li&gt;
&lt;li&gt;Docker to Ollama&lt;/li&gt;
&lt;li&gt;API to Qdrant&lt;/li&gt;
&lt;li&gt;Browser to Open WebUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those connections work, debugging the application layer becomes much easier.&lt;/p&gt;
&lt;p&gt;The MyZubster AI integration is still evolving, but the infrastructure is now ready for the next phase: building and testing the RAG workflow.&lt;/p&gt;
&lt;p&gt;What local model and vector database are you using for your AI projects?&lt;/p&gt;
&lt;h2 id=&quot;docker-artificialintelligence-python-ollama-mistral-qdrant-rag&quot;&gt;Docker #ArtificialIntelligence #Python #Ollama #Mistral #Qdrant #RAG&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26086/how-i-connected-a-dockerized-python-api-to-a-local-mistral-model</guid>
<pubDate>Wed, 02 Sep 2026 17:04:05 +0000</pubDate>
</item>
<item>
<title>Nicola’s MyZubster Pilot Now Runs Locally with Docker—and We Verified the Data Survives</title>
<link>https://coderlegion.com/26067/nicolas-myzubster-pilot-now-runs-locally-with-docker-and-we-verified-the-data-survives</link>
<description>&lt;h2 id=&quot;nicola-s-myzubster-pilot-now-runs-locally-with-docker-and-we-verified-the-data-survives&quot;&gt;Nicola’s MyZubster Pilot Now Runs Locally with Docker—and We Verified the Data Survives&lt;/h2&gt;
&lt;p&gt;Today, Nicola’s independent &lt;strong&gt;MyZubster MVP&lt;/strong&gt; completed another verifiable development cycle.&lt;/p&gt;
&lt;p&gt;The objective was not merely to build a Docker image.&lt;/p&gt;
&lt;p&gt;We wanted to prove the complete local workflow:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clone → Build → Start → Create → Persist → Restart → Recreate → Verify&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We completed every step on Nicola’s Windows computer.&lt;/p&gt;
&lt;h3 id=&quot;what-the-project-already-had&quot;&gt;What the project already had&lt;/h3&gt;
&lt;p&gt;The MVP exposes a small observation API.&lt;/p&gt;
&lt;p&gt;Its first verified user story was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create an observation → HTTP 201 → JSON persistence → API retrieval → automated test&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That implementation was reviewed and merged through a public pull request:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nicolaususnicola-lgtm/myzubster-mvp/pull/1&quot;&gt;PR #1 — Verify observation creation, persistence and retrieval&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The next question was:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can someone run the same capability locally without manually configuring the Python environment?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;making-the-environment-reproducible&quot;&gt;Making the environment reproducible&lt;/h3&gt;
&lt;p&gt;We added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Python slim Docker image;&lt;/li&gt;
&lt;li&gt;a non-root application user;&lt;/li&gt;
&lt;li&gt;Docker Compose configuration;&lt;/li&gt;
&lt;li&gt;a persistent volume;&lt;/li&gt;
&lt;li&gt;an API health check;&lt;/li&gt;
&lt;li&gt;configurable host, port and data-file location;&lt;/li&gt;
&lt;li&gt;Windows-oriented setup instructions;&lt;/li&gt;
&lt;li&gt;Docker verification in GitHub Actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The work was reviewed in:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nicolaususnicola-lgtm/myzubster-mvp/pull/2&quot;&gt;PR #2 — Add reproducible Docker setup for local development&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub Actions successfully:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;built the image;&lt;/li&gt;
&lt;li&gt;started the container;&lt;/li&gt;
&lt;li&gt;called the API;&lt;/li&gt;
&lt;li&gt;created an observation;&lt;/li&gt;
&lt;li&gt;retrieved the saved observation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After the CI passed, the pull request was merged.&lt;/p&gt;
&lt;h3 id=&quot;the-first-problem-was-not-in-the-code&quot;&gt;The first problem was not in the code&lt;/h3&gt;
&lt;p&gt;Nicola cloned the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;git clone https://github.com/nicolaususnicola-lgtm/myzubster-mvp.git
cd myzubster-mvp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first Compose command failed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;failed to connect to the docker API at
npipe:////./pipe/dockerDesktopLinuxEngine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker’s command-line tools were installed, but the Docker Desktop Linux engine was not running.&lt;/p&gt;
&lt;p&gt;We checked and updated WSL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;wsl --version
wsl --status
wsl --update
wsl --shutdown
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After Docker Desktop was started, &lt;code&gt;docker info&lt;/code&gt; confirmed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux containers;&lt;/li&gt;
&lt;li&gt;WSL 2;&lt;/li&gt;
&lt;li&gt;eight CPUs;&lt;/li&gt;
&lt;li&gt;approximately 15.5 GB of RAM;&lt;/li&gt;
&lt;li&gt;a functioning Docker server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This distinction was important: reinstalling or changing the repository would not have fixed the problem.&lt;/p&gt;
&lt;h3 id=&quot;starting-myzubster&quot;&gt;Starting MyZubster&lt;/h3&gt;
&lt;p&gt;Nicola ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose up --build -d
docker compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image was built and the API started successfully.&lt;/p&gt;
&lt;p&gt;We then created an observation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;$body = @{
  description = &quot;Prima osservazione Docker di Nicola&quot;
  latitude = 44.0678
  longitude = 12.5695
} | ConvertTo-Json

Invoke-RestMethod `
  -Method Post `
  -Uri http://localhost:5000/api/observation `
  -ContentType &quot;application/json&quot; `
  -Body $body
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API returned an observation with ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;b8e8d66b9bf5bd16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It included the description, coordinates and a UTC timestamp.&lt;/p&gt;
&lt;h3 id=&quot;verifying-persistence&quot;&gt;Verifying persistence&lt;/h3&gt;
&lt;p&gt;Reading the collection returned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;count&quot;: 1,
  &quot;observations&quot;: [
    {
      &quot;coordinates&quot;: {
        &quot;lat&quot;: 44.0678,
        &quot;lng&quot;: 12.5695
      },
      &quot;description&quot;: &quot;Prima osservazione Docker di Nicola&quot;,
      &quot;id&quot;: &quot;b8e8d66b9bf5bd16&quot;,
      &quot;media_hash&quot;: &quot;&quot;,
      &quot;timestamp&quot;: &quot;2026-09-02T08:51:57.067451+00:00&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then restarted the API service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose restart api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same observation remained available.&lt;/p&gt;
&lt;p&gt;But a container restart was not enough to prove the full persistence model.&lt;/p&gt;
&lt;h3 id=&quot;testing-down-up&quot;&gt;Testing &lt;code&gt;down → up&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;We removed the Compose environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The container stopped, while the named volume remained:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;local     myzubster-mvp_observations-data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We deliberately avoided:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose down -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because &lt;code&gt;-v&lt;/code&gt; would delete the data volume.&lt;/p&gt;
&lt;p&gt;We then recreated the environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the API returned, we queried it again.&lt;/p&gt;
&lt;p&gt;The result still contained:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;count: 1
id: b8e8d66b9bf5bd16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The observation survived both the container restart and the complete Compose recreation.&lt;/p&gt;
&lt;h3 id=&quot;a-problem-we-still-found&quot;&gt;A problem we still found&lt;/h3&gt;
&lt;p&gt;During shutdown, the Flask development server did not terminate gracefully within Docker’s initial timeout.&lt;/p&gt;
&lt;p&gt;The container eventually exited with code &lt;code&gt;137&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This did not delete the volume or corrupt the observation, but it exposed the next improvement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;replace Flask’s development server with Gunicorn;&lt;/li&gt;
&lt;li&gt;verify graceful container shutdown;&lt;/li&gt;
&lt;li&gt;repeat the persistence test;&lt;/li&gt;
&lt;li&gt;add that behavior to CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The application is an MVP, not a production deployment, and the logs correctly say so.&lt;/p&gt;
&lt;h3 id=&quot;what-has-actually-been-proven&quot;&gt;What has actually been proven&lt;/h3&gt;
&lt;p&gt;We can now verify that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the public repository can be cloned on Windows;&lt;/li&gt;
&lt;li&gt;Docker builds the application;&lt;/li&gt;
&lt;li&gt;Compose starts the API;&lt;/li&gt;
&lt;li&gt;a health check confirms availability;&lt;/li&gt;
&lt;li&gt;the API returns HTTP 201 for a valid observation;&lt;/li&gt;
&lt;li&gt;the observation is persisted;&lt;/li&gt;
&lt;li&gt;it can be retrieved through the API;&lt;/li&gt;
&lt;li&gt;it survives a service restart;&lt;/li&gt;
&lt;li&gt;it survives &lt;code&gt;docker compose down&lt;/code&gt; followed by &lt;code&gt;up&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;no passwords or access tokens are required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is more valuable than saying “Docker support added.”&lt;/p&gt;
&lt;p&gt;It is evidence that the workflow is reproducible.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-for-myzubster&quot;&gt;Why this matters for MyZubster&lt;/h3&gt;
&lt;p&gt;MyZubster is exploring whether an ecosystem can scale through independently developed capabilities instead of only accumulating users.&lt;/p&gt;
&lt;p&gt;Nicola’s MVP is still small.&lt;/p&gt;
&lt;p&gt;But it now has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public source code;&lt;/li&gt;
&lt;li&gt;a verified API workflow;&lt;/li&gt;
&lt;li&gt;automated tests;&lt;/li&gt;
&lt;li&gt;Docker packaging;&lt;/li&gt;
&lt;li&gt;persistent local storage;&lt;/li&gt;
&lt;li&gt;successful CI;&lt;/li&gt;
&lt;li&gt;evidence reproduced on the project owner’s computer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the development model we are testing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build → Validate → Connect → Measure → Verify → Replicate → Expand&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;one-human-one-ai&quot;&gt;One human + one AI&lt;/h3&gt;
&lt;p&gt;The collaboration follows a human-controlled model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nicola owns and runs his project;&lt;/li&gt;
&lt;li&gt;the AI assists with analysis, implementation and troubleshooting;&lt;/li&gt;
&lt;li&gt;GitHub records the changes;&lt;/li&gt;
&lt;li&gt;CI verifies the automated workflow;&lt;/li&gt;
&lt;li&gt;local execution proves that the result works outside the development environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AI does not decide that the software is finished.&lt;/p&gt;
&lt;p&gt;The evidence tells us what works, while the remaining failures tell us what to improve next.&lt;/p&gt;
&lt;h3 id=&quot;next-step&quot;&gt;Next step&lt;/h3&gt;
&lt;p&gt;The next technical milestone is a graceful production-style container lifecycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add Gunicorn;&lt;/li&gt;
&lt;li&gt;verify clean shutdown;&lt;/li&gt;
&lt;li&gt;preserve the current persistent volume behavior;&lt;/li&gt;
&lt;li&gt;repeat the local Windows test;&lt;/li&gt;
&lt;li&gt;record the result publicly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For now, Nicola can accurately say:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;MyZubster runs locally with Docker and preserves its observation data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Repository:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nicolaususnicola-lgtm/myzubster-mvp&quot;&gt;github.com/nicolaususnicola-lgtm/myzubster-mvp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Main ecosystem:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Website:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://myzubster.com&quot;&gt;myzubster.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What do you consider sufficient evidence that a Dockerized MVP is genuinely reproducible?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26067/nicolas-myzubster-pilot-now-runs-locally-with-docker-and-we-verified-the-data-survives</guid>
<pubDate>Wed, 02 Sep 2026 09:00:51 +0000</pubDate>
</item>
<item>
<title>What Self Hosting An AI Agent Platform Actually Costs</title>
<link>https://coderlegion.com/25778/what-self-hosting-an-ai-agent-platform-actually-costs</link>
<description>&lt;p&gt;A lot of the self hosting questions on here turn out to be hardware questions rather than software questions, and the answers usually skip the boring part. This is the version I wish someone had handed me before I rented the first box for an always on agent setup.&lt;/p&gt;
&lt;h3 id=&quot;the-software-is-free-the-server-is-not&quot;&gt;The Software Is Free, The Server Is Not&lt;/h3&gt;
&lt;p&gt;Most of the agent platforms worth running are a Docker install. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.autolearningagents.com/hosting/&quot;&gt;Auto Learning Agents&lt;/a&gt; is one of them, so the license cost is zero and the entire recurring cost is the machine underneath. That sounds obvious until you price it, because the machine is the part that decides whether the thing is usable at all.&lt;/p&gt;
&lt;p&gt;The reason you want a server in the first place is uptime. An agent that only runs while your laptop is open is a script. An agent that runs on a box with root access, comes back after a reboot, and keeps working while you sleep is a platform. That difference is entirely infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;what-the-box-actually-needs&quot;&gt;What The Box Actually Needs&lt;/h3&gt;
&lt;p&gt;Root access is not optional, because the install assumes it. Past that, the requirement splits on one decision: are you calling a hosted model API, or running the model locally?&lt;/p&gt;
&lt;p&gt;If you are calling an API, the platform itself is light. A cheap VPS handles a single user setup comfortably and CPU matters more than anything exotic.&lt;/p&gt;
&lt;p&gt;If you are running the model locally, RAM becomes the binding constraint before anything else does. A quantized 7B model wants roughly 8GB just to sit resident, and it climbs quickly from there. The common mistake is sizing the box for the platform and forgetting that the model has to live in the same memory.&lt;/p&gt;
&lt;h3 id=&quot;where-people-get-burned&quot;&gt;Where People Get Burned&lt;/h3&gt;
&lt;p&gt;Three things, roughly in the order they bite.&lt;/p&gt;
&lt;p&gt;Disk fills up. Logs, model weights and container layers all grow, and nothing warns you until a write fails.&lt;/p&gt;
&lt;p&gt;Memory gets oversubscribed. Two models resident at once on a box sized for one, and the OOM killer picks the process you cared about.&lt;/p&gt;
&lt;p&gt;Nothing restarts. The host reboots for a kernel update and the agent quietly stays down for a day because the container was never set to come back on its own.&lt;/p&gt;
&lt;p&gt;None of these are hard problems. They are just invisible until they happen to you.&lt;/p&gt;
&lt;h3 id=&quot;start-small-and-upgrade-when-it-earns-it&quot;&gt;Start Small And Upgrade When It Earns It&lt;/h3&gt;
&lt;p&gt;The honest advice is to start on the smallest box that fits the model you picked, run it for a week, and watch memory and disk before scaling anything. Most single user setups never outgrow that. Sizing up front for load you do not have yet is the most common way to make a free platform expensive.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25778/what-self-hosting-an-ai-agent-platform-actually-costs</guid>
<pubDate>Sun, 30 Aug 2026 02:03:11 +0000</pubDate>
</item>
<item>
<title>One File, Six Services: Running AI Agents With Docker Compose</title>
<link>https://coderlegion.com/25561/one-file-six-services-running-ai-agents-with-docker-compose</link>
<description>&lt;p&gt;Most of us here already reach for Compose the moment a project grows past a single process, and agent work hits that point faster than almost anything else. What follows is the shape that keeps showing up once an agent stops being a script and starts being a system.&lt;/p&gt;
&lt;h3 id=&quot;an-agent-is-never-one-process&quot;&gt;An Agent Is Never One Process&lt;/h3&gt;
&lt;p&gt;A working agent needs a runtime process, a model endpoint, a database for conversation history and agent state, and usually a vector store for retrieval. Add a queue once tasks run in the background, and something for traces once you need to know why a run went sideways. That is six services before anyone has written an interesting tool.&lt;/p&gt;
&lt;p&gt;Running those by hand means remembering startup order, wiring hostnames and ports, and keeping six sets of environment variables in sync. Compose turns all of it into one declarative file with &lt;code&gt;depends_on&lt;/code&gt; and health checks, so &lt;code&gt;docker compose up&lt;/code&gt; produces the same stack on a laptop and on a server, and &lt;code&gt;docker compose down&lt;/code&gt; leaves nothing behind.&lt;/p&gt;
&lt;p&gt;The dependency isolation matters more here than in most stacks. Agent code pins specific versions of Python, PyTorch, and CUDA, and those pins conflict constantly. Two containers can run CUDA 12.4 and CUDA 11.8 side by side because each one carries its own dependency tree.&lt;/p&gt;
&lt;h3 id=&quot;three-patterns-three-cost-profiles&quot;&gt;Three Patterns, Three Cost Profiles&lt;/h3&gt;
&lt;p&gt;The first pattern is API-backed. The agent container calls a hosted model, and the Compose file holds the runtime, a database, and whatever tools the agent needs. Minimal local resources, best available models, and a bill that scales linearly with usage.&lt;/p&gt;
&lt;p&gt;The second runs a model server such as Ollama or vLLM in the same stack. The server holds the model in GPU memory and exposes an OpenAI compatible endpoint on the internal Docker network, so the agent reaches it by service name and configuration stays trivial. Per-call cost goes to zero and nothing leaves your infrastructure, but you own the GPU memory budget, and a large model can want anywhere from 20 to 80 GB of VRAM depending on parameter count and quantization.&lt;/p&gt;
&lt;p&gt;The third is a hybrid that routes cheap high-volume calls to the local server and hard calls out to a frontier model. It is the pattern most teams land on eventually, and it is also the one that makes the routing logic worth testing.&lt;/p&gt;
&lt;h3 id=&quot;the-parts-that-bite-later&quot;&gt;The Parts That Bite Later&lt;/h3&gt;
&lt;p&gt;GPU access needs the device reservation in the Compose file and the container toolkit on the host, and it fails in a way that looks like a slow CPU run rather than an error. State needs named volumes, because a bind mount to a host path is the fastest way to lose a vector index during a rebuild. Service discovery is free inside the Compose network, which is exactly why it is worth being deliberate about which ports you publish to the host at all, since an unauthenticated model server on 0.0.0.0 is a real problem.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.autolearningagents.com/docker-ai-agents/&quot;&gt;This guide to deploying AI agents with Docker Compose&lt;/a&gt; covers each of those in depth, along with the compose file anatomy and the move from a development stack to a production one.&lt;/p&gt;
&lt;h3 id=&quot;where-compose-stops-being-the-answer&quot;&gt;Where Compose Stops Being The Answer&lt;/h3&gt;
&lt;p&gt;Compose is a single-host tool. When you need rolling updates across machines, autoscaling on queue depth, or real multi-tenancy, that is the Kubernetes conversation. Until then, a Compose file is the honest description of your architecture, and it is the artifact a new developer can read in two minutes and run in one command.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25561/one-file-six-services-running-ai-agents-with-docker-compose</guid>
<pubDate>Thu, 27 Aug 2026 23:49:58 +0000</pubDate>
</item>
<item>
<title>Why Your Simulation Does Not Reproduce On Another Machine</title>
<link>https://coderlegion.com/25551/why-your-simulation-does-not-reproduce-on-another-machine</link>
<description>&lt;p&gt;Most of us here ship code that has to behave the same on someone else's machine, and we lean on containers and lockfiles to get there. Scientific code has the same problem with a nastier failure mode, because when it breaks nothing crashes, the program just returns different numbers.&lt;/p&gt;
&lt;h3 id=&quot;three-errors-not-one&quot;&gt;Three Errors, Not One&lt;/h3&gt;
&lt;p&gt;Numerical code carries three distinct kinds of error and each one needs a different response. Round-off error comes from finite precision, since IEEE 754 doubles hold about 15 significant decimal digits and subtracting nearly equal values throws most of them away. Truncation error comes from replacing a continuous operation with a discrete one, which is what a finite difference or a time step actually is. Modeling error is the gap between the equations and reality, and no amount of grid refinement touches it.&lt;/p&gt;
&lt;p&gt;Refining the discretization fixes the second one, makes the first one slightly worse by adding operations, and does nothing at all for the third. Most arguments about a simulation being wrong turn out to be arguments about which of the three someone is looking at. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/scientific-computing/&quot;&gt;This guide to scientific computing methods&lt;/a&gt; walks through each of them next to the numerical methods they come from.&lt;/p&gt;
&lt;h3 id=&quot;why-the-same-code-gives-different-answers&quot;&gt;Why The Same Code Gives Different Answers&lt;/h3&gt;
&lt;p&gt;Change the core count and the summation order changes. Switch GPU models and the reduction tree changes. Let the compiler vectorize differently and the rounding changes. Different math libraries return different last digits for the same transcendental function. Every one of those is a legal, standards conforming result, and in a chaotic system the difference grows exponentially instead of staying in the noise.&lt;/p&gt;
&lt;p&gt;That is why bitwise reproduction is the wrong target for a lot of scientific code. The reproducible quantity is the ensemble average with error bars, not the individual trajectory.&lt;/p&gt;
&lt;h3 id=&quot;verification-is-not-validation&quot;&gt;Verification Is Not Validation&lt;/h3&gt;
&lt;p&gt;Verification asks whether you solved the equations correctly, which is a software question. Is the method implemented without bugs, and does it converge at the order it claims? Validation asks whether you solved the right equations, which is a physics question you answer against experiment.&lt;/p&gt;
&lt;p&gt;The method of manufactured solutions is the technique worth stealing here. You choose a solution first, substitute it into the governing equations to generate whatever source terms make it exact, then check that the code converges to it at the expected rate. That turns looks about right into a pass or fail test, which is exactly what unit tests do for ordinary code.&lt;/p&gt;
&lt;h3 id=&quot;what-actually-fixes-it&quot;&gt;What Actually Fixes It&lt;/h3&gt;
&lt;p&gt;The habits are ones we already have, applied to the whole environment rather than just the source. Git pins the code version behind each figure. Docker or Singularity pin the libraries and the OS layer. Snakemake or Nextflow record parameters and step order so a run can be replayed instead of remembered. Regression tests catch the day a refactor moves a result by 1e-9, which is the change nobody notices until a reviewer asks about it.&lt;/p&gt;
&lt;p&gt;None of that is exotic. It is the same reproducibility discipline we expect from a build, pointed at a program whose output is a number instead of a binary.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25551/why-your-simulation-does-not-reproduce-on-another-machine</guid>
<pubDate>Thu, 27 Aug 2026 13:59:32 +0000</pubDate>
</item>
<item>
<title>Why safe vs. fast is a false choice for platform teams</title>
<link>https://coderlegion.com/25111/why-safe-vs-fast-is-a-false-choice-for-platform-teams</link>
<description>&lt;p&gt;Modern cloud architecture is facing a massive identity crisis. We are asking infrastructure to run workloads that software engineers a decade ago would have deemed hopelessly ill-advised. Third-party user plugins executing arbitrary code, autonomous AI agents calling APIs and compiling dynamic scripts on the fly, and densely packed multi-tenant clusters where direct competitors share the exact same physical silicon. To cope, the industry defaulted to the same old, traditional security primitives like Kubernetes namespaces, seccomp profiles, AppArmor rules, and in some cases, complex system call allowlists. But all these tools rely on the same outdated assumption that we can easily predict what a workload is going to do before it does it.&lt;/p&gt;
&lt;p&gt;Namespaces are an organisational abstraction, not a security boundary. Every standard Docker container share a single Linux kernel. This is the ultimate single point of failure and the broadest attack surface in modern computing. A single container escape or kernel panic doesn't just crash a pod, it also has the blast radius to compromise every tenant on that same node. To avoid this, teams usually default to heavy, expensive alternatives like cluster-per-tenant sprawl, dated VM overhead that destroys margins, or strict syscall allowlists that break non-deterministic AI agents. Thankfully, Edera was built to eliminate this tradeoff.&lt;/p&gt;
&lt;p&gt;Operating as a Type-1 hypervisor, &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://edera.dev/&quot;&gt;Edera&lt;/a&gt;&lt;/strong&gt; gives every workload its own isolated Linux kernel in a lightweight execution boundary called a Zone. It delivers VM-grade architectural isolation at bare-metal speed (&amp;lt;1% CPU overhead, 766ms startup time) with zero hardware virtualisation dependencies (VT-x) required. The focus of this blog post was to discuss how Edera redefines the core value drivers of modern infrastructure for the workloads that many operations teams simply call &quot;too risky.&quot;&lt;/p&gt;
&lt;h3 id=&quot;multi-tenancy-for-kubernetes&quot;&gt;Multi-tenancy for Kubernetes&lt;/h3&gt;
&lt;p&gt;Kubernetes namespaces give you a false sense of security. They organise your workloads, but at the system level, every container on a node communicates directly with the host Linux kernel. When a vulnerability like a kernel privilege escalation hits, namespace boundaries evaporate. A container escape provides a direct line to every other tenant sharing that node. To avoid this shared-kernel threat, enterprise engineering teams traditionally resort to cluster-per-tenant architectures. While this solves the security problem, it introduces massive operational friction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Endless patching cycles &lt;/li&gt;
&lt;li&gt;Doubling or tripling cloud control planes&lt;/li&gt;
&lt;li&gt;Splintered monitoring and/or logging stacks&lt;/li&gt;
&lt;li&gt;Escalating cloud infrastructure bills that decimate gross margins&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Edera eliminates the shared host kernel entirely. By deploying as a hardened runtime in just two lines of YAML, Edera wraps every Kubernetes pod in its own isolated Zone powered by a dedicated Linux kernel.&lt;br&gt;
If a workload panics, only its specific Zone crashes. If a malicious process attempts a container escape, it hits a Type-1 hypervisor boundary with no host on the other side. Network traffic is similarly hardened: every workload receives a cryptographic identity via &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://edera.dev/stories/edera-and-confidential-computing-stronger-together&quot;&gt;SPIFFE/SPIRE&lt;/a&gt;&lt;/strong&gt;, paired with private networking that ensures packets never touch the host interface. The result is a 60% reduction in node sprawl and total immunity against shared-kernel CVEs. This design achieves true multi-tenant density on shared hardware without taking on shared risk.&lt;/p&gt;
&lt;h3 id=&quot;multi-tenancy-for-gpus&quot;&gt;Multi-tenancy for GPUs&lt;/h3&gt;
&lt;p&gt;GPUs are the most expensive, underutilised asset in the modern datacentre. The vast majority of enterprise GPU clusters operate at a dismal 20% to 30% resource utilisation. Inference workloads idle while waiting for requests, model development tasks consume full cards for basic interactive research, and multi-tenant sharing is routinely blocked by security and compliance teams. Why? Because traditional GPU sharing mechanisms (like MIG or process-level slicing) lack hard isolation boundaries. If two competing teams or customers share a single physical GPU, a driver-level exploit or memory leak in one application can crash the entire card or read sensitive model weights from adjacent memory.&lt;/p&gt;
&lt;p&gt;Edera extends hypervisor-level isolation directly down to physical accelerator hardware, allowing organisations to securely partition GPUs into right-sized virtual GPUs (vGPUs) across multiple tenants.  Edera allows teams to safely drive GPU utilisation from 20–30% up to 60–80% by co-locating dense inference models and fractional development environments on the same card.&lt;/p&gt;
&lt;p&gt;In traditional systems, all containers must share the host’s exact GPU driver and kernel version. Edera allows different kernel versions to run per Zone. You can run legacy ML frameworks requiring older drivers alongside cutting-edge setups on the same physical server. Likewise, memory and compute partitions are isolated at the hypervisor boundary. If an ML job triggers a CUDA out-of-memory error or driver panic, it is contained inside its Zone without impacting neighbouring workloads on the GPU.&lt;/p&gt;
&lt;h3 id=&quot;ai-agent-execution&quot;&gt;AI agent execution&lt;/h3&gt;
&lt;p&gt;AI agents are fundamentally changing software architecture. Unlike legacy microservices that execute predictable, pre-compiled code paths, autonomous agents are non-deterministic by nature. They write and run dynamic scripts, execute terminal commands, browse the web, parse unpredictable web inputs, and chain complex tools together in real time. This introduces a massive security dilemma. Traditional container security relies on predicting behaviour:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system call allowlists block unexpected behaviours.&lt;/li&gt;
&lt;li&gt;seccomp profiles dictate the allowable system calls.&lt;/li&gt;
&lt;li&gt;AppArmor / SELinux restrict filesystem access based on known paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Attempting to enforce rigid system call allow-lists on an autonomous agent running tools like Claude Code, LangChain, CrewAI, or AutoGen will inevitably break the agent. But running agents with unrestricted host kernel access invites severe prompt injection, arbitrary code execution, and lateral network movement. Edera shifts this paradigm from restricting agents to rightfully containing them.&lt;/p&gt;
&lt;p&gt;Instead of trying to predict every system call an agent might make, Edera grants the agent an isolated Zone with a complete, fully functional Linux kernel supporting 350+ standard system calls. The agent gets complete operational autonomy to compile code, invoke binaries, and manage processes. However, because the Zone is structurally isolated by Edera’s hypervisor, the blast radius of a compromised or rogue agent is mathematically bounded to exactly one workload.&lt;/p&gt;
&lt;p&gt;With a rapid 766ms Zone startup time, developers can dynamically spin up an isolated environment for a single agent execution step and tear it down immediately after. This ends up delivering near-native speed without risking your host infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;untrusted-code-execution&quot;&gt;Untrusted code execution&lt;/h3&gt;
&lt;p&gt;Whether processing customer-submitted Python scripts, executing webhooks, running third-party plugins, or compiling LLM-generated code snippets, running untrusted code is a core requirement for modern SaaS platforms. Historically, engineering teams faced three bad choices for untrusted code execution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The shared kernel in Docker and Kubernetes offered fast performance, but extreme security risk since a single kernel vulnerability can lead to an entire host takeover.&lt;/li&gt;
&lt;li&gt;The system call interception approach seen in gVisor worked. It's safe, but incurs heavy performance penalties (up to 59% build overhead) and frequent application breakages due to missing syscalls.&lt;/li&gt;
&lt;li&gt;The traditional  MicroVM  isolation method, as seen with Kata containers, provides a secure VM boundary, which is a significant improvement, but requires nested hardware virtualisation (VT-x), incurring high cold-start times (~2.5x slower) and blocking deployment on 93% of public cloud instances.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this context, Edera eliminates the trade-off between performance, security, and cloud portability:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1) Near-native speeds&lt;/strong&gt;: &lt;br&gt;
Operates with less than 1% CPU overhead and somewhere around 5% build overhead.&lt;br&gt;
Delivers up to 12x faster performance than gVisor under heavy network and I/O stress.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2) Support for over 350 syscalls:&lt;/strong&gt;&lt;br&gt;
Complete syscall compatibility means zero application debugging or allowlist maintenance. &lt;br&gt;
The code runs exactly as you would expect it to run on bare metal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3) Extensive cloud compatibility&lt;/strong&gt;: &lt;br&gt;
Because Edera does not require hardware VT-x extensions, it offers 93% cloud instance coverage.&lt;br&gt;
Runs natively on standard public cloud VMs, bare-metal nodes, and edge devices alike.&lt;/p&gt;
&lt;h3 id=&quot;trust-compliance-and-confidential-computing-cryptographic-proof-of-isolation-&quot;&gt;Trust, compliance, and confidential computing (Cryptographic proof of isolation)&lt;/h3&gt;
&lt;p&gt;In highly regulated sectors such as fintech, healthcare, and the defence sector, security controls must be provable to auditors, auditors' tools, and prospective customers. Claiming that &quot;&lt;em&gt;our software is isolated by policy&lt;/em&gt;&quot; no longer passes vendor risk assessments. Furthermore, traditional cloud security models protect data at rest and in transit, but leave data exposed in use inside host memory. While hardware-based Trusted Execution Environments (TEEs) and Confidential Computing solve parts of this puzzle, they often demand specialised hardware, proprietary SDKs, and significant application refactoring.&lt;/p&gt;
&lt;p&gt;Every Edera Zone is assigned an immutable, cryptographic identity via SPIFFE/SPIRE. Security teams can generate verifiable cryptographic audit trails proving that workloads executed inside isolated, untrusted boundaries. TEEs (like AMD SEV or Intel SGX) protect workloads from a compromised host hypervisor. Edera protects the infrastructure and neighbouring tenants from a compromised workload. Together, Edera and TEEs provide complete, end-to-end confidential computing without requiring specialised hardware for every standard pod. Furthermore, Edera’s core architectural isolation primitives achieved zero critical findings during a comprehensive 4-week public security audit conducted by &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://edera.dev/stories/security-built-into-our-dna-how-edera-achieves-enterprise-grade-protection&quot;&gt;Trail of Bits&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;observability-amp-tuning&quot;&gt;Observability &amp;amp; tuning&lt;/h3&gt;
&lt;p&gt;A major downside of traditional virtualisation and sandboxing technologies is the &quot;black box&quot; effect. When you wrap a container inside a heavy VM, you frequently lose deep observability. Standard host-level agents (like eBPF probes, Prometheus exporters, or APMs) struggle to see inside the guest environment, forcing teams to inject monitoring sidecars into every single tenant container. This again increases the memory overhead and telemetry complexity.&lt;/p&gt;
&lt;p&gt;Edera delivers enhanced runtime observability that gives platform operators deep, real-time visibility into workload behaviour without sacrificing security boundaries. See exactly what system resources, memory channels, and I/O operations every workload is consuming in real time from the hypervisor layer. Instead of drowning in thousands of noisy runtime alert logs (&quot;&lt;em&gt;Alert: suspicious /etc read attempt&lt;/em&gt;&quot; from Falco), Edera’s structural isolation prevents lateral movement and privilege escalation at the boundary layer. Finally, track true CPU/RAM consumption per Zone to right-size multi-tenant pods, optimise GPU memory allocation, and eliminate waste across cloud fleets with total confidence.&lt;/p&gt;
&lt;p&gt;My closing statement is this - the next decade of cloud computing will definitely belong to platform teams that are able to run complex, untrusted, and non-deterministic workloads safely at scale. Choosing between developer velocity, cloud spend margins, and foundational security is a false choice born from the legacy way of thinking when everyone relied on a shared-kernel architecture. By replacing the shared host kernels with cryptographically verified, hypervisor-isolated Zones, Edera lets you move fast, cut cloud spend, and run the next-gen AI workloads without the fear of unexpected behaviour.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25111/why-safe-vs-fast-is-a-false-choice-for-platform-teams</guid>
<pubDate>Fri, 21 Aug 2026 21:24:10 +0000</pubDate>
</item>
<item>
<title>How to Give AI Agents a Safe Sandbox Using Docker</title>
<link>https://coderlegion.com/24394/how-to-give-ai-agents-a-safe-sandbox-using-docker</link>
<description>&lt;p&gt;AI agents that can execute code, browse files, or run shell commands are powerful - and risky. Docker sandboxes let you hand an agent real execution capability without letting it touch anything it shouldn't.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disposable Containers as Agent Workspaces&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When an AI agent needs to run code or interact with a filesystem, the naive approach is to let it run directly on the host. That works until it doesn't - a runaway loop, an unintended file deletion, or a dependency conflict can wreck your environment.&lt;/p&gt;
&lt;p&gt;The better pattern is to spin up a fresh Docker container per agent task, give it only what it needs, and throw it away when the task is done. Each container provides an isolated workspace: it has its own filesystem, its own process namespace, and no access to host resources unless you explicitly grant them. The agent runs inside it, does its work, and the container is removed. If something goes wrong, the blast radius is one disposable container, not your machine or your production environment.&lt;/p&gt;
&lt;p&gt;This pattern is especially useful for agents that use tool-calling (where the LLM triggers actual function execution) or code interpreter steps - any workflow where the model's output becomes a live command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real Example: Spin Up and Tear Down an Agent Sandbox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's a minimal pattern using the Docker SDK for Python to create a sandboxed execution environment for an agent task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import docker

client = docker.from_env()

def run_in_sandbox(code: str) -&amp;gt; str:
 container = client.containers.run(
 image=&quot;python:3.12-slim&quot;,
 command=[&quot;python&quot;, &quot;-c&quot;, code],
 mem_limit=&quot;128m&quot;,
 network_disabled=True,
 remove=True, # auto-delete after exit
 stdout=True,
 stderr=True,
 )
 return container.decode(&quot;utf-8&quot;)

output = run_in_sandbox(&quot;print(sum(range(100)))&quot;)
print(output) # 4950
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few details worth noting: &lt;code&gt;network_disabled=True&lt;/code&gt; cuts off outbound calls from inside the container - critical if the agent-generated code might try to exfiltrate data or call external APIs unexpectedly. &lt;code&gt;mem_limit&lt;/code&gt; prevents a runaway process from consuming host memory. &lt;code&gt;remove=True&lt;/code&gt; means the container is deleted the moment it exits, so you're not accumulating stale containers.&lt;/p&gt;
&lt;p&gt;For more complex agent setups (multi-step tasks, file I/O between steps), you can mount a temporary volume for the duration of the task and unmount it afterward, keeping the isolation intact while still allowing artifact passing between agent turns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Giving AI agents a throwaway Docker container per task limits damage from unexpected or malicious code execution.&lt;/li&gt;
&lt;li&gt;Disabling networking and capping memory are the two most important constraints to set from the start - not as an afterthought.&lt;/li&gt;
&lt;li&gt;This pattern works with most agent frameworks (LangChain, LlamaIndex, custom tool-calling loops) since it wraps execution at the infrastructure layer, not inside the framework itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are you currently running agent-generated code directly on the host, or do you already have an isolation layer in place - and if so, what does it look like?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sources referenced: HackerNews - Docker Sandboxes discussion (403 points, 258 comments)&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24394/how-to-give-ai-agents-a-safe-sandbox-using-docker</guid>
<pubDate>Mon, 10 Aug 2026 15:21:23 +0000</pubDate>
</item>
<item>
<title>Docker Desktop Is the New Adobe CC: Bloated &amp; Expensive</title>
<link>https://coderlegion.com/23994/docker-desktop-is-the-new-adobe-cc-bloated-expensive</link>
<description>&lt;p&gt;Let's start with an uncomfortable question. When's the last time you actually looked at what's running when you open Docker Desktop, versus what you think is running? Because I did, and it ruined my week a little, so now it's going to ruin yours too.&lt;/p&gt;
&lt;h3 id=&quot;from-lightweight-alternative-to-vms-to-the-thing-it-was-built-to-replace&quot;&gt;From &quot;lightweight alternative to VMs&quot; to the thing it was built to replace&lt;/h3&gt;
&lt;p&gt;Here's the pitch Docker sold everyone around 2013: forget heavy virtual machines, forget booting a whole guest OS just to run one process, containers share the host kernel and give you VM-like isolation for a fraction of the overhead. Genuinely great idea. Genuinely changed how the entire industry ships software. Nobody's arguing with the concept.&lt;/p&gt;
&lt;p&gt;The problem is what happened to the &lt;em&gt;implementation&lt;/em&gt; on the two operating systems most developers actually use day to day. On macOS and Windows, Docker Desktop runs every single container inside a hidden Linux virtual machine — LinuxKit on the backend, spun up the moment you launch the app. Yes, really: the tool whose entire founding pitch was &quot;you don't need a VM anymore&quot; ships you one anyway, just quietly, behind a whale icon, so you don't have to think about it. It's giving &quot;we don't sell your data, we just process it&quot; energy, and if that sentence means something to you, you already know exactly the kind of institutional gaslighting I'm talking about.&lt;/p&gt;
&lt;p&gt;That VM idles at somewhere around 1-4GB of RAM doing absolutely nothing — no containers running, no builds happening, just existing, breathing, judging you from the menu bar. On Windows the overhead is worse: Microsoft's own docs flag roughly an extra 4GB of memory requirement specifically because you're running a VM on top of a host that isn't natively Linux. Compare that to a native Linux box, where the &quot;VM&quot; is just... the kernel you already have. Docker on Linux doesn't need this charade. Docker on your MacBook does, and it's been quietly costing you a chunk of your RAM budget since the day you installed it.&lt;/p&gt;
&lt;h3 id=&quot;the-gui-is-a-browser-pretending-to-be-a-dashboard&quot;&gt;The GUI is a browser pretending to be a dashboard&lt;/h3&gt;
&lt;p&gt;Here's the part that should genuinely make you angry once you know it: Docker Desktop's dashboard — the pretty window that shows you a list of running containers and their logs — is an Electron app. Which means, under the hood, it's Chromium. A full web browser. Docker's own public roadmap confirms it outright, and their own release notes have shipped fixes for the dashboard throwing &lt;code&gt;ERR_FAILED&lt;/code&gt; errors because process-hardening policies conflicted with Chromium — an error message that should not exist in a tool whose entire job is &quot;show text in a box.&quot; Security researchers have literally opened the Chrome DevTools console inside Docker Desktop using a Konami-code-style key combo, because it's Chromium under a trench coat, and Chromium ships DevTools.&lt;/p&gt;
&lt;p&gt;Sit with that for a second. The tool that replaced heavyweight virtual machines with &quot;lightweight&quot; containers is now shipping you a second heavyweight virtual machine, wrapped in a second heavyweight browser engine, so you can look at logs you could've read with &lt;code&gt;docker logs -f&lt;/code&gt; and a text editor that's been installed on your machine since 2004.&lt;/p&gt;
&lt;h3 id=&quot;the-years-long-soap-opera-of-just-reading-a-file&quot;&gt;The years-long soap opera of just... reading a file&lt;/h3&gt;
&lt;p&gt;If the VM and the browser-in-a-box weren't enough, let's talk about what happens when you mount your actual project folder into a container — something approximately every developer using Docker on a Mac does on day one. For years, the default file-sharing backend was &lt;code&gt;osxfs&lt;/code&gt;, and &lt;code&gt;osxfs&lt;/code&gt; was, to use the precise technical term, garbage. Developers running Symfony, Laravel, or anything with a bloated &lt;code&gt;node_modules&lt;/code&gt; folder would watch page loads take multiple seconds because every file read had to cross the host-to-VM boundary through a genuinely slow translation layer. Docker's answer was gRPC-FUSE, which was faster, in the same sense that being hit by a slightly smaller truck is an improvement. It took until 2022 for VirtioFS to finally ship as a real fix, and Docker's own benchmarks claimed up to 98% faster file sync versus the old backend.&lt;/p&gt;
&lt;p&gt;Here's the punchline: even with VirtioFS, independent benchmarks still show it running 2-5x slower than native Linux for workloads with lots of small files — which describes precisely the &lt;code&gt;node_modules&lt;/code&gt; situation that made Mac developers' hair go gray in the first place. You went from &quot;unusably slow&quot; to &quot;noticeably slow,&quot; and Docker shipped a blog post celebrating it like they'd solved the problem instead of just making the tax smaller.&lt;/p&gt;
&lt;h3 id=&quot;and-then-they-figured-out-how-to-charge-you-for-the-privilege&quot;&gt;And then they figured out how to charge you for the privilege&lt;/h3&gt;
&lt;p&gt;In 2021, Docker changed the terms: Docker Desktop went from &quot;free forever, no asterisks&quot; to &quot;free unless your company crosses 250 employees or USD 10 million in revenue, in which case pay up.&quot; Fair enough, companies need revenue, I'm not going to pretend GPL-licensed goodwill pays anyone's rent. But by 2026 the tiers have fully calcified into the SaaS pricing ladder you'd expect from a project management tool, not a container runtime: Personal is free, Pro runs about USD 9 per user per month, Team lands around USD 15 per user per month, and Business tops out near USD 24 per user per month for the privilege of SSO, audit logs, and something called &quot;Hardened Docker Desktop&quot; — which is a genuinely funny name for a security feature, implying the regular one is, what, soft? Squishy? Vulnerable to a stiff breeze?&lt;/p&gt;
&lt;p&gt;Annual billing barely helps on the top tier either — Business gets effectively zero discount for committing a year in advance, which is the kind of pricing decision that only makes sense if you've already accepted that switching costs are your actual product. And here's the twist that should bother you more than the price tag: Docker's own list pricing has been creeping up 3-7% a year through 2025-2026, the exact playbook every SaaS company runs once they know you're locked in. You didn't sign up for a subscription. You signed up for a Docker Desktop-shaped mortgage.&lt;/p&gt;
&lt;p&gt;And it doesn't even wait to be asked. By default, Docker Desktop adds itself to your login items, so you open your laptop to check one email or watch one YouTube video, and somewhere behind your open tabs a multi-gigabyte VM has already quietly spun itself up and started warming your lap through the chassis for containers you have no intention of running today.&lt;/p&gt;
&lt;h3 id=&quot;meanwhile-the-actual-alternatives-got-quietly-good&quot;&gt;Meanwhile, the actual alternatives got quietly good&lt;/h3&gt;
&lt;p&gt;This is normally the part of the post where I'd tell you to just suffer through it because &quot;there's no real alternative.&quot; That stopped being true a while ago, and pretending otherwise at this point is just Stockholm syndrome with extra steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Podman&lt;/strong&gt; is the one worth taking seriously first, because it doesn't ask you to change how you think, just what daemon you trust. No background daemon running as root by default — Podman runs containers as your own user, rootless, which means a container escape doesn't hand an attacker root on your actual machine, it hands them your own limited user account, which is a considerably less exciting Tuesday for everyone involved. It speaks the same OCI image format, the CLI is close enough to Docker's that muscle memory mostly survives the switch (&lt;code&gt;alias docker=podman&lt;/code&gt; and half your team won't even notice), and it doesn't need a hidden VM babysitting a whale icon just to exist. If your objection to switching is &quot;but my &lt;code&gt;docker-compose.yml&lt;/code&gt; files,&quot; Podman Compose exists specifically to make that objection irrelevant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nix and NixOS&lt;/strong&gt; are the other end of the spectrum, and I'm not going to pretend this one is for everyone — it's for the specific flavor of masochist who reads a man page for fun and considers &lt;code&gt;make&lt;/code&gt; &quot;too much magic.&quot; Nix's whole premise is that you don't containerize your environment, you &lt;em&gt;declare&lt;/em&gt; it: a Nix expression describes exactly which packages, which versions, which dependencies your project needs, and Nix builds that environment reproducibly, byte-for-byte, on any machine running the Nix package manager — no container boundary required, because the isolation comes from content-addressed, immutable package derivations instead of kernel namespaces. It's genuinely the most correct solution to &quot;works on my machine&quot; that currently exists. It is also, in fairness, the solution most likely to have you debugging a syntax error in a language that looks like someone smashed Haskell and JSON together at 2am. Hardcore. Occasionally the right answer anyway.&lt;/p&gt;
&lt;p&gt;And if the previous two paragraphs made your palms sweat because you actually like having a window with buttons in it — fair, not everyone wants to live in a terminal. &lt;strong&gt;Colima&lt;/strong&gt; is the unofficial Mac-community hero here: it spins up a genuinely minimal VM using macOS's own native virtualization framework and hands you a clean Docker-compatible CLI, no Electron, no subscription tiers, no telemetry dashboard, at a fraction of Docker Desktop's footprint. &lt;strong&gt;Rancher Desktop&lt;/strong&gt; covers similar ground with an actual GUI attached, for people who want Podman or &lt;code&gt;dockerd&lt;/code&gt; under the hood but still want to click a button instead of memorizing flags. Neither one asks about your headcount. Neither one ships you a browser to read log lines.&lt;/p&gt;
&lt;h3 id=&quot;so-what-do-you-actually-do-with-this&quot;&gt;So what do you actually do with this&lt;/h3&gt;
&lt;p&gt;Nobody's saying rip Docker out of your CI pipeline this afternoon — for a lot of teams, the GUI, Docker Scout's vulnerability scanning, and Build Cloud's CI acceleration genuinely earn their subscription, and that's a legitimate trade to make with eyes open. But if you're a solo developer or a small team paying the Personal-tier RAM tax just to run &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker run&lt;/code&gt; locally, you are paying VM overhead and possibly a subscription fee for functionality Podman gives you for free, without the hidden VM, without the daemon running as root, and without ever once asking about your headcount.&lt;/p&gt;
&lt;p&gt;Try Podman for a week on your actual local workflow. If your muscle memory survives the &lt;code&gt;alias&lt;/code&gt;, you've just gotten your RAM back for nothing. If it doesn't — fine, go back to the whale, no judgment, some relationships are just complicated. But at least now you know exactly what you're paying for, and exactly what's quietly running behind it every time you open the app.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/23994/docker-desktop-is-the-new-adobe-cc-bloated-expensive</guid>
<pubDate>Sun, 02 Aug 2026 14:09:21 +0000</pubDate>
</item>
<item>
<title>Skopeo, Crane, and regctl: Container Image Management Without the Docker Daemon (2026)</title>
<link>https://coderlegion.com/23254/skopeo-crane-and-regctl-container-image-management-without-the-docker-daemon-2026</link>
<description>
&lt;a href=&quot;https://alexandre-vazquez.com/skopeo-crane-regctl-container-image-tools/&quot; title=&quot;Skopeo, Crane, and regctl: Container Image Management Without the Docker Daemon (2026)&quot; rel=&quot;nofollow&quot;&gt;&lt;img width=&quot;1672&quot; height=&quot;941&quot; src=&quot;https://mlk5tkvgfeoa.i.optimole.com/cb:s8WW.68/w:1672/h:941/q:mauto/f:best/https://alexandre-vazquez.com/wp-content/uploads/2026/05/skope.avif&quot; class=&quot;webfeedsFeaturedVisual wp-post-image&quot; alt=&quot;Skopeo, Crane, and regctl: Container Image Management Without the Docker Daemon (2026)&quot; style=&quot;display: block; margin: auto; margin-bottom: 5px;max-width: 100%;&quot; title=&quot;Skopeo, Crane, and regctl: Container Image Management Without the Docker Daemon (2026) 12&quot;&gt;&lt;/a&gt;&lt;h2&gt;The Problem: Docker Is Overkill for Image Operations&lt;/h2&gt;
&lt;p&gt;You need to copy an image from Docker Hub to your private registry. Or inspect a manifest before pulling. Or delete old tags programmatically. Or sync an entire repository during a migration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related reading: &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/kaniko-image-volumes-kubernetes-build-history/&quot;&gt;Kaniko, BuildKit and Image Volumes&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;


&lt;p&gt;The instinct is to reach for Docker. But Docker requires a running daemon, root access (or group membership that amounts to the same thing), and pulls the entire image to disk just to read its metadata. For CI pipelines, GitOps workflows, and platform tooling, that&amp;rsquo;s a significant overhead for what should be lightweight registry operations.&lt;/p&gt;
&lt;p&gt;This is the problem that &lt;strong&gt;daemonless container image tools&lt;/strong&gt; solve. Skopeo pioneered the category; today it has real competition from crane, regctl, and ORAS &amp;mdash; each with different strengths and ideal use cases.&lt;/p&gt;
&lt;p&gt;This article gives you the practical comparison to pick the right tool for your workflow.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Contenders&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Maintainer&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Daemon required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skopeo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Red Hat / containers&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;crane&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google / ko-build&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;regctl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;regclient&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ORAS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CNCF&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;cosign&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sigstore / OpenSSF&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All five are Go binaries, statically compiled, and work directly against the OCI Distribution Spec. None of them require Docker or any container runtime.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Skopeo&lt;/h2&gt;
&lt;p&gt;Skopeo was the first major tool to address daemonless image operations, released by Red Hat in 2016 as part of the &lt;code&gt;containers/image&lt;/code&gt; ecosystem (alongside Podman and Buildah).&lt;/p&gt;
&lt;h3&gt;What Skopeo does well&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Image inspection without pulling:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo inspect docker://registry.k8s.io/pause:3.9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Returns full image metadata &amp;mdash; digest, layers, labels, architecture, OS &amp;mdash; without downloading a single layer. Useful in admission controllers, policy checks, and pre-deployment validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-registry copying:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo copy 
  docker://docker.io/library/nginx:1.27 
  docker://harbor.internal/library/nginx:1.27
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copies image manifests and layers directly between registries, bypassing your local machine entirely. The image never touches your disk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-arch handling:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo copy --all 
  docker://docker.io/library/nginx:1.27 
  docker://harbor.internal/library/nginx:1.27
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--all&lt;/code&gt; flag copies the full manifest list, preserving all architectures (linux/amd64, linux/arm64, etc.). This is critical when mirroring images for multi-arch clusters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Registry synchronization:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo sync 
  --src docker 
  --dest docker 
  --all 
  docker.io/library/nginx 
  harbor.internal/mirrors/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;skopeo sync&lt;/code&gt; mirrors an entire repository, including all tags. You can also use a YAML file to define which images and tags to sync &amp;mdash; useful for air-gapped environment bootstrapping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tag deletion:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo delete docker://harbor.internal/myapp:old-tag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful in CI for cleanup pipelines. Note that registry-side deletion requires the registry to have the DELETE method enabled.&lt;/p&gt;
&lt;h3&gt;Skopeo&amp;rsquo;s weaknesses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No image modification&lt;/strong&gt;: Skopeo copies and inspects, but doesn&amp;rsquo;t build or modify images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag listing is verbose&lt;/strong&gt;: &lt;code&gt;skopeo list-tags&lt;/code&gt; returns JSON you need to parse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No retry logic by default&lt;/strong&gt;: transient network errors in long sync operations require wrapping with retry scripts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth configuration&lt;/strong&gt;: relies on &lt;code&gt;containers/auth.json&lt;/code&gt; format, which differs from Docker&amp;rsquo;s &lt;code&gt;~/.docker/config.json&lt;/code&gt; (though it supports both)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to use Skopeo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Air-gapped environment image mirroring&lt;/li&gt;
&lt;li&gt;CI pipelines that need to copy or inspect images without Docker&lt;/li&gt;
&lt;li&gt;Platform teams on Red Hat / OpenShift stacks&lt;/li&gt;
&lt;li&gt;Any workflow already using Podman or Buildah&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Crane&lt;/h2&gt;
&lt;p&gt;Crane is Google&amp;rsquo;s answer to Skopeo, developed as part of the &lt;a href=&quot;https://ko.build/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;ko&lt;/a&gt; project and later extracted into its own tool. It&amp;rsquo;s simpler, more scriptable, and has a cleaner CLI design.&lt;/p&gt;
&lt;h3&gt;What Crane does well&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Tag listing:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane ls registry.k8s.io/pause
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No JSON parsing needed. One tag per line. Pipe directly into &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Digest resolution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane digest docker.io/library/nginx:1.27
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Returns the image digest. Combine with &lt;code&gt;yq&lt;/code&gt; or &lt;code&gt;sed&lt;/code&gt; to pin image references in Helm values or Kubernetes manifests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Image copying:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane cp docker.io/library/nginx:1.27 harbor.internal/library/nginx:1.27
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same capability as Skopeo&amp;rsquo;s copy, arguably with a cleaner syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Manifest inspection:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane manifest docker.io/library/nginx:1.27 | jq .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Returns raw manifest JSON. Useful when you need the exact manifest for digest verification or policy enforcement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tagging and retagging:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane tag harbor.internal/myapp:abc123 harbor.internal/myapp:stable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adds a new tag to an existing image without re-uploading layers. The tag operation is purely a manifest pointer update.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flattening images:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crane flatten docker.io/library/ubuntu:24.04 -t harbor.internal/ubuntu:flat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Squashes all layers into one. Reduces layer count for images where layer history doesn&amp;rsquo;t matter.&lt;/p&gt;
&lt;h3&gt;Crane&amp;rsquo;s weaknesses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No sync command&lt;/strong&gt;: unlike Skopeo, crane has no built-in repository sync. You script it yourself with &lt;code&gt;crane ls&lt;/code&gt; + &lt;code&gt;crane cp&lt;/code&gt; in a loop&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less mature multi-arch support&lt;/strong&gt;: &lt;code&gt;crane cp&lt;/code&gt; supports multi-arch but the UX is less explicit than Skopeo&amp;rsquo;s &lt;code&gt;--all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No delete command&lt;/strong&gt;: doesn&amp;rsquo;t implement registry deletion&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to use Crane&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CI/CD scripting where you want clean, pipeable output&lt;/li&gt;
&lt;li&gt;Digest pinning workflows&lt;/li&gt;
&lt;li&gt;Lightweight image tagging operations&lt;/li&gt;
&lt;li&gt;When you&amp;rsquo;re already in the ko / Google Cloud ecosystem&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;regctl&lt;/h2&gt;
&lt;p&gt;Regctl is the least known of the three but arguably the most feature-complete. It&amp;rsquo;s the CLI for the &lt;code&gt;regclient&lt;/code&gt; Go library and covers use cases that Skopeo and crane leave out.&lt;/p&gt;
&lt;h3&gt;What regctl does uniquely well&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Image modification without rebuild:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;regctl image mod myimage:tag 
  --label &quot;org.opencontainers.image.version=1.2.3&quot; 
  --replace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can add/change labels, annotations, and config fields directly on an existing image in the registry &amp;mdash; without pulling, rebuilding, or pushing a new image. This is impossible with Skopeo or crane.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer operations:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Remove a specific layer from an image
regctl image mod myimage:tag 
  --layer-rm sha256:abc123... 
  --replace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful for removing accidentally included secrets or large unnecessary layers from published images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OCI artifact support:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;regctl artifact put 
  --media-type application/vnd.example.config.v1+json 
  --config config.json 
  file.tar.gz 
  harbor.internal/myartifacts:v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Regctl has solid OCI artifact support alongside standard image operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formatting and output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;regctl tag list harbor.internal/myapp --format '{{range .}}{{println .}}{{end}}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go template formatting throughout. Useful for integrating into shell scripts without jq.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Referrers (OCI 1.1):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;regctl manifest get-list harbor.internal/myapp:v1 --referrers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lists referrers (signatures, SBOMs, attestations) attached to an image via the OCI 1.1 referrers API.&lt;/p&gt;
&lt;h3&gt;Regctl&amp;rsquo;s weaknesses&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Smaller community&lt;/strong&gt;: fewer examples, less StackOverflow coverage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Steeper learning curve&lt;/strong&gt;: more commands, more flags&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less packaging&lt;/strong&gt;: not in most distro repos by default&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to use regctl&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Image post-processing (labels, annotations, layer removal) without rebuild&lt;/li&gt;
&lt;li&gt;Advanced manifest and referrer workflows&lt;/li&gt;
&lt;li&gt;When you need OCI artifact operations alongside image operations&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;ORAS&lt;/h2&gt;
&lt;p&gt;ORAS (OCI Registry As Storage) is a CNCF project focused specifically on OCI artifact management &amp;mdash; pushing and pulling arbitrary files to container registries, not necessarily container images.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Push a Helm chart as an OCI artifact
oras push harbor.internal/charts/myapp:1.0.0 
  --artifact-type application/vnd.helm.chart.v1+tar 
  mychart.tgz

# Push SBOM
oras push harbor.internal/myapp:v1 
  --artifact-type application/spdx+json 
  sbom.spdx.json

# Pull
oras pull harbor.internal/charts/myapp:1.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ORAS is not a direct Skopeo replacement &amp;mdash; it&amp;rsquo;s for when your registry is a general-purpose artifact store, not just a container registry. Helm OCI, SBOMs, attestations, and policy bundles all benefit from ORAS.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;cosign&lt;/h2&gt;
&lt;p&gt;Cosign from Sigstore is not a general-purpose image tool &amp;mdash; it&amp;rsquo;s specifically for supply chain security. But it&amp;rsquo;s increasingly part of any container image workflow.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Sign an image
cosign sign --key cosign.key harbor.internal/myapp:v1@sha256:abc123...

# Verify
cosign verify --key cosign.pub harbor.internal/myapp:v1

# Attach SBOM
cosign attach sbom --sbom sbom.spdx harbor.internal/myapp:v1

# Keyless signing (Sigstore)
cosign sign harbor.internal/myapp:v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cosign integrates with OIDC providers for keyless signing (no key management required), which is the direction the ecosystem is moving. If you&amp;rsquo;re building a supply chain security practice, cosign is mandatory, not optional.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Side-by-side comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Skopeo&lt;/th&gt;
&lt;th&gt;Crane&lt;/th&gt;
&lt;th&gt;regctl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inspect image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inspect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;manifest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;manifest get&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image copy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy all arches&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copy --all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cp&lt;/code&gt; (auto)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image copy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sync repository&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;script it&lt;/td&gt;
&lt;td&gt;script it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List tags&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list-tags&lt;/code&gt; (JSON)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ls&lt;/code&gt; (plain)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tag list&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete tag/image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tag delete&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modify labels&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image mod&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove layer&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image mod&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCI artifacts&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;&lt;code&gt;artifact&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referrers (1.1)&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;manifest get-list&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Practical workflows&lt;/h2&gt;
&lt;h3&gt;Mirror images for air-gapped clusters (Skopeo)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# sync-list.yaml
docker.io:
  images:
    library/nginx:
      - &quot;1.25&quot;
      - &quot;1.26&quot;
      - &quot;1.27&quot;
    library/redis:
      - &quot;7.2&quot;
      - &quot;7.4&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;skopeo sync 
  --src yaml 
  --dest docker 
  --all 
  sync-list.yaml 
  harbor.internal/mirrors/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pin image digests in CI (Crane)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# Update image digests in values.yaml
for image in nginx:1.27 redis:7.4; do
  digest=$(crane digest docker.io/library/${image})
  echo &quot;docker.io/library/${image}@${digest}&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combine with &lt;code&gt;yq&lt;/code&gt; to update Helm values files automatically, ensuring reproducible deployments.&lt;/p&gt;
&lt;h3&gt;Retag without re-pushing (Crane or regctl)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# After a successful deploy to staging, promote to production
crane tag harbor.internal/myapp:${GIT_SHA} harbor.internal/myapp:production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No layer transfer. The operation is a metadata update in the registry.&lt;/p&gt;
&lt;h3&gt;Add OCI annotations post-build (regctl)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;regctl image mod harbor.internal/myapp:v1.2.3 
  --annotation &quot;org.opencontainers.image.source=&lt;a href=&quot;https://github.com/org/repo&quot; rel=&quot;nofollow&quot;&gt;https://github.com/org/repo&lt;/a&gt;&quot; 
  --annotation &quot;org.opencontainers.image.revision=${GIT_SHA}&quot; 
  --replace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Attaches build metadata to an image already in the registry, without a rebuild.&lt;/p&gt;
&lt;h3&gt;Supply chain security pipeline&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. Build and push
docker buildx build --push -t harbor.internal/myapp:${GIT_SHA} .

# 2. Generate SBOM
syft harbor.internal/myapp:${GIT_SHA} -o spdx-json &amp;gt; sbom.spdx.json

# 3. Attach SBOM
cosign attach sbom --sbom sbom.spdx.json harbor.internal/myapp:${GIT_SHA}

# 4. Sign (keyless with OIDC in CI)
cosign sign harbor.internal/myapp:${GIT_SHA}

# 5. Verify in admission controller or deployment pipeline
cosign verify 
  --certificate-identity-regexp=&quot;&lt;a href=&quot;https://github.com/org/repo&quot; rel=&quot;nofollow&quot;&gt;https://github.com/org/repo&lt;/a&gt;&quot; 
  --certificate-oidc-issuer=&quot;&lt;a href=&quot;https://token.actions.githubusercontent.com&quot; rel=&quot;nofollow&quot;&gt;https://token.actions.githubusercontent.com&lt;/a&gt;&quot; 
  harbor.internal/myapp:${GIT_SHA}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;All tools install as single static binaries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Skopeo (via package manager)
brew install skopeo                    # macOS
dnf install skopeo                     # RHEL/Fedora
apt install skopeo                     # Debian/Ubuntu

# Crane
brew install crane
# or binary release
curl -sL &lt;a href=&quot;https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz&quot; rel=&quot;nofollow&quot;&gt;https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz&lt;/a&gt; | tar xz crane

# regctl
curl -sL &lt;a href=&quot;https://github.com/regclient/regclient/releases/latest/download/regctl.linux.amd64&quot; rel=&quot;nofollow&quot;&gt;https://github.com/regclient/regclient/releases/latest/download/regctl.linux.amd64&lt;/a&gt; -o /usr/local/bin/regctl
chmod +x /usr/local/bin/regctl

# ORAS
brew install oras

# cosign
brew install cosign
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Which tool should you use?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use Skopeo if:&lt;/strong&gt; you&amp;rsquo;re on a Red Hat / OpenShift stack, you need repository sync, or you&amp;rsquo;re building air-gapped environment pipelines. It&amp;rsquo;s the most battle-tested and widely packaged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Crane if:&lt;/strong&gt; you&amp;rsquo;re scripting image operations in CI and want clean, composable CLI output. &lt;code&gt;crane ls&lt;/code&gt; + &lt;code&gt;crane cp&lt;/code&gt; + &lt;code&gt;crane digest&lt;/code&gt; cover 80% of automation use cases with minimal friction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use regctl if:&lt;/strong&gt; you need to modify images post-build, work with OCI referrers, or want the most complete feature set for a registry client. It has a higher learning curve but can replace both Skopeo and crane for advanced workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use ORAS if:&lt;/strong&gt; you&amp;rsquo;re using a registry to store non-image artifacts &amp;mdash; Helm charts, SBOMs, policy bundles, ML models.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cosign&lt;/strong&gt; regardless of which of the above you pick, as soon as supply chain security matters to your organization. It&amp;rsquo;s not a replacement for the others &amp;mdash; it&amp;rsquo;s a complement.&lt;/p&gt;
&lt;p&gt;In practice, most platform teams end up using 2-3 of these tools together. Crane for day-to-day scripting, Skopeo for sync jobs, cosign for signing, ORAS for artifact storage.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Can I use these tools with private registries?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. All support standard registry authentication. Crane and Skopeo both read from &lt;code&gt;~/.docker/config.json&lt;/code&gt;. Regctl has its own config file (&lt;code&gt;~/.regctl/config.json&lt;/code&gt;) but can import Docker credentials. Set &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; to point to your credentials file in CI environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do these work with Docker Hub rate limits?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, and they&amp;rsquo;re often more efficient than the Docker CLI because they only fetch manifest metadata for inspect operations, not full layers. For heavy pull workloads, authenticate with your Docker Hub credentials to get higher rate limits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about ECR, GCR, and Azure Container Registry?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All tools support these with the appropriate credential helpers. For ECR, use &lt;code&gt;docker-credential-ecr-login&lt;/code&gt;. Crane has native ECR support via the &lt;code&gt;--platform&lt;/code&gt; flag and &lt;code&gt;crane auth&lt;/code&gt; commands. Skopeo supports ECR via &lt;code&gt;--creds &quot;AWS:$(aws ecr get-login-password)&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Are these tools safe to run in Kubernetes pods?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. Since they require no daemon and no elevated privileges for read operations, they&amp;rsquo;re well-suited to r&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/23254/skopeo-crane-and-regctl-container-image-management-without-the-docker-daemon-2026</guid>
<pubDate>Tue, 28 Jul 2026 10:00:00 +0000</pubDate>
</item>
<item>
<title>5 Docker Security Mistakes That Leave Your Applications Exposed</title>
<link>https://coderlegion.com/23566/5-docker-security-mistakes-that-leave-your-applications-exposed</link>
<description>&lt;h2 id=&quot;5-docker-security-mistakes-that-leave-your-applications-exposed&quot;&gt;5 Docker Security Mistakes That Leave Your Applications Exposed&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Docker · Security · DevOps · Self-Hosted · Web Security&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Docker has become the default way to deploy modern applications. Whether you're running a personal project, an internal dashboard, or a production service, spinning up a container takes only a few minutes.&lt;/p&gt;
&lt;p&gt;The problem is that many developers unconsciously assume a containerized application is also a secure application.&lt;/p&gt;
&lt;p&gt;It isn't.&lt;/p&gt;
&lt;p&gt;Docker provides process isolation and simplifies deployment, but it doesn't protect your application from HTTP-based attacks, automated scanners, credential stuffing, or malicious bots. Those problems exist at the application layer, long after packets have reached your server.&lt;/p&gt;
&lt;p&gt;Over the past few years, I've reviewed dozens of Docker deployments—from personal side projects to production systems maintained by small engineering teams. Although every stack looks different, the same security issues appear again and again.&lt;/p&gt;
&lt;p&gt;This article covers five of the most common mistakes, why they matter, and how you can improve your security posture without dramatically increasing operational complexity.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-assuming-docker-isolation-is-enough&quot;&gt;1. Assuming Docker Isolation Is Enough&lt;/h2&gt;
&lt;p&gt;Docker does an excellent job of isolating processes, but container isolation should never be confused with application security.&lt;/p&gt;
&lt;p&gt;A container can still expose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vulnerable web applications&lt;/li&gt;
&lt;li&gt;insecure APIs&lt;/li&gt;
&lt;li&gt;weak authentication&lt;/li&gt;
&lt;li&gt;exposed administration interfaces&lt;/li&gt;
&lt;li&gt;outdated software&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If an attacker reaches your application through HTTP, Docker has already done its job. Everything that happens after that depends on how your application and infrastructure are configured.&lt;/p&gt;
&lt;p&gt;A common deployment looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Internet
      │
      ▼
Reverse Proxy
      │
      ▼
Docker Container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From Docker's perspective, everything is working correctly.&lt;/p&gt;
&lt;p&gt;From an attacker's perspective, the application is still directly reachable.&lt;/p&gt;
&lt;h4 id=&quot;best-practice&quot;&gt;Best Practice&lt;/h4&gt;
&lt;p&gt;Treat Docker as an application packaging tool, not a security boundary.&lt;/p&gt;
&lt;p&gt;Application-layer protection should be considered separately from container isolation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-believing-https-equals-security&quot;&gt;2. Believing HTTPS Equals Security&lt;/h2&gt;
&lt;p&gt;This is probably one of the most common misconceptions.&lt;/p&gt;
&lt;p&gt;HTTPS encrypts traffic.&lt;/p&gt;
&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; determine whether requests are legitimate.&lt;/p&gt;
&lt;p&gt;Once a TLS connection has been established, your server still has to process requests such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /.env
GET /.git/config
GET /phpmyadmin/
GET /wp-login.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every public-facing application eventually receives requests like these.&lt;/p&gt;
&lt;p&gt;Sometimes they're vulnerability scanners.&lt;/p&gt;
&lt;p&gt;Sometimes they're botnets.&lt;/p&gt;
&lt;p&gt;Sometimes they're simply automated tools searching for common weaknesses.&lt;/p&gt;
&lt;p&gt;TLS protects confidentiality during transmission.&lt;/p&gt;
&lt;p&gt;It doesn't stop malicious HTTP requests from reaching your application.&lt;/p&gt;
&lt;h4 id=&quot;best-practice-1&quot;&gt;Best Practice&lt;/h4&gt;
&lt;p&gt;Enable HTTPS by default, but don't mistake encryption for security.&lt;/p&gt;
&lt;p&gt;Application-layer threats require application-layer defenses.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-ignoring-automated-bot-traffic&quot;&gt;3. Ignoring Automated Bot Traffic&lt;/h2&gt;
&lt;p&gt;Many developers imagine attackers manually selecting targets.&lt;/p&gt;
&lt;p&gt;Reality is much less exciting.&lt;/p&gt;
&lt;p&gt;Most Internet-facing services are discovered automatically.&lt;/p&gt;
&lt;p&gt;Bots continuously scan IPv4 address space looking for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exposed CMS installations&lt;/li&gt;
&lt;li&gt;leaked environment files&lt;/li&gt;
&lt;li&gt;known framework vulnerabilities&lt;/li&gt;
&lt;li&gt;default login pages&lt;/li&gt;
&lt;li&gt;outdated dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your application doesn't need to be popular.&lt;/p&gt;
&lt;p&gt;It only needs to respond.&lt;/p&gt;
&lt;p&gt;One surprising lesson from operating public services is that attack traffic usually appears within minutes of exposing a new server.&lt;/p&gt;
&lt;p&gt;Even relatively small applications often receive thousands of automated requests before receiving their first legitimate users.&lt;/p&gt;
&lt;h4 id=&quot;best-practice-2&quot;&gt;Best Practice&lt;/h4&gt;
&lt;p&gt;Don't optimize only for legitimate traffic.&lt;/p&gt;
&lt;p&gt;Plan for automated traffic as part of normal Internet operations.&lt;/p&gt;
&lt;p&gt;Bot filtering and rate limiting should be considered baseline protections rather than optional features.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-depending-entirely-on-infrastructure-security&quot;&gt;4. Depending Entirely on Infrastructure Security&lt;/h2&gt;
&lt;p&gt;Most deployment guides spend a lot of time discussing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;firewalls&lt;/li&gt;
&lt;li&gt;private networks&lt;/li&gt;
&lt;li&gt;SSH hardening&lt;/li&gt;
&lt;li&gt;Docker networks&lt;/li&gt;
&lt;li&gt;VPNs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all important.&lt;/p&gt;
&lt;p&gt;However, they primarily protect infrastructure.&lt;/p&gt;
&lt;p&gt;Modern attacks increasingly target applications instead.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL injection&lt;/li&gt;
&lt;li&gt;Cross-site scripting&lt;/li&gt;
&lt;li&gt;malicious request payloads&lt;/li&gt;
&lt;li&gt;authentication abuse&lt;/li&gt;
&lt;li&gt;credential stuffing&lt;/li&gt;
&lt;li&gt;API enumeration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Infrastructure security alone cannot identify these behaviors because they often appear as perfectly valid HTTP requests.&lt;/p&gt;
&lt;p&gt;This is where a Web Application Firewall (WAF) becomes useful.&lt;/p&gt;
&lt;p&gt;Rather than protecting the operating system, it focuses on inspecting and filtering requests before they reach your application.&lt;/p&gt;
&lt;p&gt;One practical deployment looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Internet
      │
      ▼
Self-hosted WAF
      │
      ▼
Nginx / Traefik / Caddy
      │
      ▼
Docker Applications
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Placing the WAF in front of your reverse proxy allows suspicious traffic to be inspected before it reaches backend services.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-i-chose-a-self-hosted-waf&quot;&gt;Why I Chose a Self-Hosted WAF&lt;/h2&gt;
&lt;p&gt;Cloud WAF providers are excellent products.&lt;/p&gt;
&lt;p&gt;For many organizations, they're probably the right choice.&lt;/p&gt;
&lt;p&gt;They typically provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CDN integration&lt;/li&gt;
&lt;li&gt;DDoS mitigation&lt;/li&gt;
&lt;li&gt;managed infrastructure&lt;/li&gt;
&lt;li&gt;simplified deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, my requirements were slightly different.&lt;/p&gt;
&lt;p&gt;I wanted application-layer protection that stayed inside my own infrastructure.&lt;/p&gt;
&lt;p&gt;More importantly, I wanted visibility.&lt;/p&gt;
&lt;p&gt;I wanted to understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What requests actually reached my servers?&lt;/li&gt;
&lt;li&gt;Which endpoints were being scanned most frequently?&lt;/li&gt;
&lt;li&gt;What kinds of payloads appeared every day?&lt;/li&gt;
&lt;li&gt;Which rules should be adjusted for my own environment?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For self-hosted services and internal applications, keeping inspection close to the application made more sense than relying entirely on a cloud provider.&lt;/p&gt;
&lt;p&gt;After evaluating several open-source options, I settled on &lt;strong&gt;SafeLine WAF&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not because it's the only capable solution, but because it offered a good balance between deployment simplicity, application visibility, and operational overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-never-looking-at-your-logs&quot;&gt;5. Never Looking at Your Logs&lt;/h2&gt;
&lt;p&gt;Many teams only investigate logs after something breaks.&lt;/p&gt;
&lt;p&gt;Unfortunately, by that point the interesting events have already happened.&lt;/p&gt;
&lt;p&gt;One unexpected benefit of introducing a WAF wasn't simply blocking requests—it was making malicious traffic visible.&lt;/p&gt;
&lt;p&gt;Instead of reading thousands of raw access log entries, patterns became obvious.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;repeated scans targeting WordPress installations on non-WordPress servers&lt;/li&gt;
&lt;li&gt;automated requests searching for exposed Git repositories&lt;/li&gt;
&lt;li&gt;SQL injection payloads against nonexistent endpoints&lt;/li&gt;
&lt;li&gt;brute-force login attempts&lt;/li&gt;
&lt;li&gt;bots cycling through known vulnerability lists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Individually, these events weren't particularly alarming.&lt;/p&gt;
&lt;p&gt;Collectively, they revealed how noisy the public Internet really is.&lt;/p&gt;
&lt;p&gt;Visibility changes the way you operate systems.&lt;/p&gt;
&lt;p&gt;Once you understand normal background traffic, unusual behavior becomes much easier to identify.&lt;/p&gt;
&lt;h4 id=&quot;best-practice-3&quot;&gt;Best Practice&lt;/h4&gt;
&lt;p&gt;Security tools shouldn't only block attacks.&lt;/p&gt;
&lt;p&gt;They should also help you understand your environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;a-practical-example&quot;&gt;A Practical Example&lt;/h2&gt;
&lt;p&gt;One of my current projects runs on a fairly standard VPS.&lt;/p&gt;
&lt;p&gt;The infrastructure is intentionally simple.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ubuntu Server

├── Docker Engine
├── SafeLine WAF
├── Nginx
├── PostgreSQL
└── Application Containers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There isn't a dedicated security team.&lt;/p&gt;
&lt;p&gt;There isn't a SIEM platform.&lt;/p&gt;
&lt;p&gt;There aren't enterprise security products running everywhere.&lt;/p&gt;
&lt;p&gt;The goal is simply to reduce unnecessary risk while keeping operational overhead manageable.&lt;/p&gt;
&lt;p&gt;In this environment, placing SafeLine in front of the reverse proxy provides three practical benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;filtering obvious malicious requests before they reach applications&lt;/li&gt;
&lt;li&gt;applying centralized rate limiting across multiple services&lt;/li&gt;
&lt;li&gt;providing visibility into application-layer traffic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these eliminate the need for secure coding or proper authentication.&lt;/p&gt;
&lt;p&gt;They simply add another defensive layer without significantly complicating deployment.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-a-self-hosted-waf-may-not-be-necessary&quot;&gt;When a Self-Hosted WAF May Not Be Necessary&lt;/h2&gt;
&lt;p&gt;A self-hosted WAF isn't the right answer for every environment.&lt;/p&gt;
&lt;p&gt;You may not need one if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every application is only accessible through a VPN&lt;/li&gt;
&lt;li&gt;your infrastructure is completely private&lt;/li&gt;
&lt;li&gt;you're already using an enterprise-managed cloud security platform&lt;/li&gt;
&lt;li&gt;your team doesn't want to maintain another infrastructure component&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Self-hosting always introduces additional operational responsibility.&lt;/p&gt;
&lt;p&gt;Updates.&lt;/p&gt;
&lt;p&gt;Monitoring.&lt;/p&gt;
&lt;p&gt;Configuration management.&lt;/p&gt;
&lt;p&gt;Backups.&lt;/p&gt;
&lt;p&gt;These trade-offs should be considered before introducing any new infrastructure component.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-4&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Docker dramatically simplifies deployment, but it doesn't eliminate the need for application security.&lt;/p&gt;
&lt;p&gt;As applications become Internet-facing, automated scanning and malicious traffic quickly become part of everyday operations.&lt;/p&gt;
&lt;p&gt;The goal isn't to build an impenetrable system.&lt;/p&gt;
&lt;p&gt;That's unrealistic.&lt;/p&gt;
&lt;p&gt;Instead, the goal is to make attacks more difficult, reduce unnecessary exposure, and improve visibility into what's happening inside your infrastructure.&lt;/p&gt;
&lt;p&gt;For smaller engineering teams, a practical security strategy doesn't need dozens of products.&lt;/p&gt;
&lt;p&gt;It starts with understanding where your current defenses stop.&lt;/p&gt;
&lt;p&gt;Docker isolates processes.&lt;/p&gt;
&lt;p&gt;HTTPS encrypts traffic.&lt;/p&gt;
&lt;p&gt;Firewalls protect networks.&lt;/p&gt;
&lt;p&gt;A Web Application Firewall fills a different role by inspecting application-layer requests before they reach your services.&lt;/p&gt;
&lt;p&gt;Choosing between a cloud WAF and a self-hosted solution ultimately depends on your architecture and operational preferences.&lt;/p&gt;
&lt;p&gt;If you value local control, application visibility, and protecting self-hosted services without relying entirely on external infrastructure, a solution like SafeLine WAF is well worth evaluating—not as a replacement for good security practices, but as another layer in a defense-in-depth strategy.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker isolation does not equal application security.&lt;/li&gt;
&lt;li&gt;HTTPS encrypts traffic but does not block malicious requests.&lt;/li&gt;
&lt;li&gt;Automated bots continuously scan every public-facing service.&lt;/li&gt;
&lt;li&gt;Infrastructure security and application security solve different problems.&lt;/li&gt;
&lt;li&gt;Visibility is one of the most valuable security capabilities you can add.&lt;/li&gt;
&lt;li&gt;A self-hosted WAF can provide application-layer protection while keeping security controls close to your own infrastructure.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23566/5-docker-security-mistakes-that-leave-your-applications-exposed</guid>
<pubDate>Mon, 27 Jul 2026 09:27:01 +0000</pubDate>
</item>
<item>
<title>Coolify: The Complete Manual Setup Guide (For When the Auto-Install Script Won't Cut It)</title>
<link>https://coderlegion.com/23307/coolify-the-complete-manual-setup-guide-for-when-the-auto-install-script-wont-cut-it</link>
<description>&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=jlUzYm6W-bI&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/VIDEO_ID_HERE/0.jpg&quot; alt=&quot;Watch the video&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Coolify's one-line install script is great — until it isn't. Right now it officially supports Ubuntu 20.04, 22.04, and 24.04 LTS. If you're running anything newer (Ubuntu's already on 26.04 LTS), the script won't work and you're left doing it manually.&lt;/p&gt;
&lt;p&gt;This is that manual walkthrough — set up in the order that fits a security-first VPS workflow rather than the order Coolify's own docs use. If you've been following along with the Ansible playbooks from earlier in this series, this picks up right where that left off.&lt;/p&gt;
&lt;h3 id=&quot;minimum-hardware-requirements&quot;&gt;Minimum Hardware Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU:&lt;/strong&gt; 2 cores&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory:&lt;/strong&gt; 2 GB RAM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; 30 GB free&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coolify can technically run below this, but it's not recommended.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before touching Coolify itself, you'll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSH access to your VPS&lt;/li&gt;
&lt;li&gt;CURL installed&lt;/li&gt;
&lt;li&gt;Docker Engine installed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're reconnecting to a server you've rebuilt or re-provisioned, clear the old fingerprint first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -f '/home/your-path/.ssh/known_hosts' -R 'your-vps-ip'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;installing-ssh&quot;&gt;Installing SSH&lt;/h4&gt;
&lt;p&gt;If you followed the earlier videos in this series, OpenSSH is already installed. If not:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install -y openssh-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Confirm it's running and check which port it's listening on (you should have already changed this from the default 22 — see the VPS security video):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status ssh
sudo ss -tulpn | grep ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;installing-curl&quot;&gt;Installing CURL&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install -y curl
curl --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;ca-certificates&lt;/code&gt; also get installed as part of the &lt;code&gt;apt-update&lt;/code&gt; Ansible playbook below, so this may already be handled.&lt;/p&gt;
&lt;h3 id=&quot;running-the-first-ansible-playbook&quot;&gt;Running the First Ansible Playbook&lt;/h3&gt;
&lt;p&gt;Connect Ansible to the VPS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ANSIBLE_HOST_KEY_CHECKING=FALSE ansible -i ./inventory/hosts vpsDemo -m ping --user root --ask-pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the update playbook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook ./playbooks/apt-update.yml --user root -e &quot;ansible_port=22&quot; --ask-pass --ask-become-pass -i ./inventory/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you haven't set up the Ansible inventory and playbooks from the earlier videos, do that first — this guide assumes they're already in place.&lt;/p&gt;
&lt;h3 id=&quot;installing-docker-engine&quot;&gt;Installing Docker Engine&lt;/h3&gt;
&lt;p&gt;Remove any conflicting packages first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add Docker's official GPG key and repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources &amp;lt;&amp;lt;EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release &amp;amp;&amp;amp; echo &quot;${UBUNTU_CODENAME:-$VERSION_CODENAME}&quot;)
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify it worked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status docker
sudo docker run hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;creating-a-non-root-admin-user&quot;&gt;Creating a Non-Root Admin User&lt;/h3&gt;
&lt;p&gt;Coolify's own docs assume you're using the root account. Since root login is disabled as part of the security hardening earlier in this series, we create a dedicated user with passwordless sudo instead.&lt;/p&gt;
&lt;p&gt;Run the second playbook to create that user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook ./playbooks/basic-secure.yml --user root -e &quot;ansible_port=22&quot; --ask-pass --ask-become-pass -i ./inventory/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then set up that user's SSH directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /home/your-sudo-user/.ssh
sudo touch /home/your-sudo-user/.ssh/authorized_keys
sudo chmod 700 /home/your-sudo-user/.ssh
sudo chmod 600 /home/your-sudo-user/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;setting-up-coolify-s-directory-structure&quot;&gt;Setting Up Coolify's Directory Structure&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance}
sudo mkdir -p /data/coolify/ssh/{keys,mux}
sudo mkdir -p /data/coolify/proxy/dynamic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;generating-and-adding-an-ssh-key&quot;&gt;Generating and Adding an SSH Key&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo ssh-keygen -f /data/coolify/ssh/keys/*Emails are not allowed*rnal -t ed25519 -N '' -C your-sudo-user@coolify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the public key to the authorized_keys file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /data/coolify/ssh/keys/*Emails are not allowed* | sudo tee -a /home/your-sudo-user/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pulling-coolify-s-configuration-files&quot;&gt;Pulling Coolify's Configuration Files&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo curl -fsSL https://cdn.coollabs.io/coolify/docker-compose.yml -o /data/coolify/source/docker-compose.yml
sudo curl -fsSL https://cdn.coollabs.io/coolify/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
sudo curl -fsSL https://cdn.coollabs.io/coolify/.env.production -o /data/coolify/source/.env
sudo curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;generating-secure-environment-values&quot;&gt;Generating Secure Environment Values&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Only run these once, on first install.&lt;/strong&gt; Changing them later can break Coolify. Back them up somewhere safe.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo sed -i &quot;s|APP_ID=.*|APP_ID=$(openssl rand -hex 16)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g&quot; /data/coolify/source/.env
sudo sed -i &quot;s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g&quot; /data/coolify/source/.env
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;permissions-and-docker-setup&quot;&gt;Permissions and Docker Setup&lt;/h3&gt;
&lt;p&gt;Set correct ownership and permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo chown -R 9999:root /data/coolify
sudo find /data/coolify -type d -exec chmod 755 {} \;
sudo find /data/coolify -type f -exec chmod 644 {} \;
sudo chown -R your-sudo-user:your-sudo-user /home/your-sudo-user/.ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the Docker network Coolify expects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo docker network create --attachable coolify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add your user to the Docker group:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo usermod -aG docker your-sudo-user
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;starting-coolify&quot;&gt;Starting Coolify&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Confirm it's running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then visit &lt;code&gt;http://YOUR-SERVER-IP:8000&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;h3 id=&quot;setup-your-ssl-certificates&quot;&gt;Setup your SSL Certificates&lt;/h3&gt;
&lt;p&gt;Coolify's reverse-proxy Traefik does this under the hood automatically.&lt;/p&gt;
&lt;p&gt;In your DNS records add two A records.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A     @     your-vps-ip-address        14400
A     *     your-vps-ip-address        14400
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your Coolify dashboard go to settings in the left side column. In the input box marked URL type your https subdomain for Coolify there.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://coolify.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Traefik will automatically apply a Let's Encrypt certificate to your Coolify subdomain.&lt;/p&gt;
&lt;h3 id=&quot;disable-port-8000&quot;&gt;Disable Port 8000&lt;/h3&gt;
&lt;p&gt;Disable port 8000 on your VPS. If your hosting provider allows you to configure a firewall from your dashboard, disable it from there. To disable port 8000, simply write rules that allow the ports you want and block everything else.&lt;/p&gt;
&lt;h3 id=&quot;wrap-up-1&quot;&gt;Wrap-Up&lt;/h3&gt;
&lt;p&gt;That's a full manual Coolify install on a hardened, non-root VPS — no automated script required. From here, Coolify handles the rest: connecting your Git repos, setting up applications, and managing deployments.&lt;/p&gt;
&lt;p&gt;If you hit issues with the automated script on a newer Ubuntu release, this manual path should get you unblocked. Questions or corrections welcome in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23307/coolify-the-complete-manual-setup-guide-for-when-the-auto-install-script-wont-cut-it</guid>
<pubDate>Thu, 23 Jul 2026 05:15:31 +0000</pubDate>
</item>
<item>
<title>I Ran terraform validate Inside a Lambda. Two Things Broke.</title>
<link>https://coderlegion.com/22978/i-ran-terraform-validate-inside-a-lambda-two-things-broke</link>
<description>&lt;p&gt;Most agent projects that generate code evaluate it by asking another model whether the code looks correct. I did that for about a week in TerraformAgent before I got tired of it.&lt;/p&gt;
&lt;p&gt;The failure mode is boring and predictable. The judge reads a &lt;code&gt;.tf&lt;/code&gt; file, sees resource blocks with plausible attribute names, and says yes. The file has an undeclared variable, or a reference to a resource that does not exist, or an attribute that HashiCorp removed in provider v5. The judge has no way to know. It is pattern-matching on what Terraform usually looks like, which is exactly what the generator was doing when it produced the bug.&lt;/p&gt;
&lt;p&gt;Terraform ships a checker. It is called &lt;code&gt;terraform validate&lt;/code&gt;. It is deterministic, it takes about a second, and it is never wrong about syntax or references. The whole reason people reach for an LLM judge here is that running the real thing inside a Lambda is annoying. It is annoying. It is also not that hard, and this post is the parts that actually bit me.&lt;/p&gt;
&lt;p&gt;TerraformAgent is a 6-node LangGraph pipeline that turns a natural-language infra request into Terraform. Orchestrator, researcher, parallel domain subagents, aggregator, reviewer, evaluator. The evaluator is the node I care about here.&lt;/p&gt;
&lt;h3 id=&quot;the-evaluator-writes-to-disk-and-shells-out&quot;&gt;The evaluator writes to disk and shells out&lt;/h3&gt;
&lt;p&gt;The generated code lives in state as a filename-to-content dict. To run real Terraform against it, you have to put it on a filesystem. Lambda gives you &lt;code&gt;/tmp&lt;/code&gt;, so that is where it goes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;thread_id = state.get(&quot;thread_id&quot;, &quot;default&quot;)
temp_dir = f&quot;/tmp/tf_eval_{thread_id}&quot;

if os.path.exists(temp_dir):
    shutil.rmtree(temp_dir)
os.makedirs(temp_dir, exist_ok=True)

aggregated_output = state.get(&quot;aggregated_output&quot;, {})
for filename, content in aggregated_output.items():
    filepath = os.path.join(temp_dir, filename)
    with open(filepath, &quot;w&quot;) as f:
        f.write(content)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;thread_id&lt;/code&gt; in the path matters. Lambda execution environments get reused across invocations, so two runs can land in the same container. Without the thread scoping, run B validates a directory containing run A's leftovers.&lt;/p&gt;
&lt;p&gt;Then three subprocess calls, in order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;fmt_result = subprocess.run(
    [&quot;terraform&quot;, &quot;fmt&quot;, &quot;-check&quot;, &quot;-recursive&quot;],
    cwd=temp_dir, capture_output=True, text=True, timeout=30,
)
eval_results[&quot;terraform_fmt_pass&quot;] = fmt_result.returncode == 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;init&lt;/code&gt; next, then &lt;code&gt;validate&lt;/code&gt; only if &lt;code&gt;init&lt;/code&gt; succeeded. Skipping that guard gives you a validate failure that is really an init failure, and the retry logic then blames the wrong thing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if init_success:
    val_result = subprocess.run(
        [&quot;terraform&quot;, &quot;validate&quot;, &quot;-no-color&quot;],
        cwd=temp_dir, capture_output=True, text=True, timeout=60,
    )
    eval_results[&quot;terraform_validate_pass&quot;] = val_result.returncode == 0
else:
    eval_results[&quot;terraform_validate_pass&quot;] = None
    eval_results[&quot;terraform_validate_output&quot;] = &quot;skipped (init failed)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the three-state result. &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt;. &lt;code&gt;None&lt;/code&gt; means the check did not run. If you collapse that to &lt;code&gt;False&lt;/code&gt; you get a retry loop chasing a validation error that was never evaluated. There is a fourth case in the &lt;code&gt;fmt&lt;/code&gt; block for the same reason: &lt;code&gt;FileNotFoundError&lt;/code&gt; sets the result to &lt;code&gt;None&lt;/code&gt; with the message &lt;code&gt;terraform binary not found (running locally without Docker?)&lt;/code&gt;, because the binary only exists inside the container image and I run these tests on my laptop.&lt;/p&gt;
&lt;h3 id=&quot;terraform-init-wants-the-network-lambda-should-not-&quot;&gt;terraform init wants the network. Lambda should not.&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt; downloads the AWS provider plugin. It is about 60 MB. Doing that on every invocation is slow, costs egress, and breaks the moment HashiCorp's release endpoint has a bad minute.&lt;/p&gt;
&lt;p&gt;So bake it into the image at build time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;RUN mkdir -p /tmp/tf-provider-cache /opt/tf-plugins &amp;amp;&amp;amp; \
    cd /tmp/tf-provider-cache &amp;amp;&amp;amp; \
    printf 'terraform {\n  required_providers {\n    aws = {\n      source  = &quot;hashicorp/aws&quot;\n      version = &quot;~&amp;gt; 5.0&quot;\n    }\n  }\n}\n' &amp;gt; main.tf &amp;amp;&amp;amp; \
    TF_PLUGIN_CACHE_DIR=/opt/tf-plugins terraform init -backend=false &amp;amp;&amp;amp; \
    rm -rf /tmp/tf-provider-cache

ENV TF_PLUGIN_CACHE_DIR=/opt/tf-plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u2ukf2iwsr4vcedaczm0.png&quot; alt=&quot;Flow diagram of TerraformAgent's evaluator node: generated files are written to a thread-scoped /tmp directory, then checked by terraform fmt, init, and validate, with the AWS provider cache copied from read-only /opt into a writable /tmp path. An LLM judge runs in parallel on task coverage. Both feed a retry decision that either re-runs flagged domains or completes.&quot;&gt;&lt;/p&gt;
&lt;p&gt;A throwaway &lt;code&gt;main.tf&lt;/code&gt; that declares nothing but the provider requirement, one &lt;code&gt;init&lt;/code&gt;, and the plugin lands in the cache directory.&lt;/p&gt;
&lt;p&gt;The cache goes in &lt;code&gt;/opt&lt;/code&gt;, not &lt;code&gt;/tmp&lt;/code&gt;. This one cost me real time. Lambda mounts a fresh empty &lt;code&gt;/tmp&lt;/code&gt; on every execution environment at runtime. Anything you write to &lt;code&gt;/tmp&lt;/code&gt; during the Docker build is gone. Not stale, not partially there. Gone. &lt;code&gt;/opt&lt;/code&gt; is part of the read-only image layers and survives to runtime.&lt;/p&gt;
&lt;h3 id=&quot;the-read-only-cache-breaks-on-version-mismatch&quot;&gt;The read-only cache breaks on version mismatch&lt;/h3&gt;
&lt;p&gt;Baking into &lt;code&gt;/opt&lt;/code&gt; fixes the download. It introduces a second problem, and this is the part I did not see coming.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/opt&lt;/code&gt; is read-only at runtime. Terraform treats &lt;code&gt;TF_PLUGIN_CACHE_DIR&lt;/code&gt; as a directory it can write to. As long as the generated code asks for a provider version matching what got baked in, Terraform reads from the cache and everyone is happy. The moment the generated code pins something else, Terraform tries to download that version &lt;em&gt;into the cache dir&lt;/em&gt;, hits a read-only path, and fails outright. Not a slow path. A hard failure.&lt;/p&gt;
&lt;p&gt;The fix is a copy, once per execution environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;baked_cache = os.environ.get(&quot;TF_PLUGIN_CACHE_DIR&quot;, &quot;/opt/tf-plugins&quot;)
writable_cache = &quot;/tmp/tf-plugins&quot;
if not os.path.isdir(writable_cache):
    if os.path.isdir(baked_cache):
        shutil.copytree(baked_cache, writable_cache)
    else:
        os.makedirs(writable_cache, exist_ok=True)

env = os.environ.copy()
env[&quot;TF_PLUGIN_CACHE_DIR&quot;] = writable_cache

init_result = subprocess.run(
    [&quot;terraform&quot;, &quot;init&quot;, &quot;-backend=false&quot;, &quot;-input=false&quot;, &quot;-no-color&quot;],
    cwd=temp_dir, capture_output=True, text=True, timeout=120, env=env,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;if not os.path.isdir(writable_cache)&lt;/code&gt; guard is what makes this cheap. Warm containers skip the copy entirely. Cold ones pay a local filesystem copy, which is nothing next to a 60 MB download. Matching versions stay fully offline. Non-matching versions still work, they just fall back to a normal network install into a directory Terraform is allowed to write to.&lt;/p&gt;
&lt;p&gt;That is the tradeoff I would call out in a review: I could have refused non-matching versions and kept it strictly offline, which would be faster and more predictable. I chose to degrade to a network fetch instead, because an agent that generates &lt;code&gt;version = &quot;~&amp;gt; 4.0&quot;&lt;/code&gt; for a good reason should not hard-fail on infrastructure trivia.&lt;/p&gt;
&lt;h3 id=&quot;the-llm-judge-is-still-there-it-just-answers-a-different-question-&quot;&gt;The LLM judge is still there. It just answers a different question.&lt;/h3&gt;
&lt;p&gt;I want to correct something, because I have seen this framed as replacing the LLM judge, and that is not what happened.&lt;/p&gt;
&lt;p&gt;The evaluator runs both. Mechanical checks answer &quot;is this valid Terraform.&quot; An LLM judge answers &quot;does this do what the user asked&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;judge_prompt = f&quot;&quot;&quot;Task: {state.get('task', '')}

Generated Terraform code:
{json.dumps(aggregated_output)}

Does the generated code satisfy the original task? Evaluate:
1. Are all requested resources present?
2. Are the configurations correct for the stated requirements?
3. Are there any obvious omissions?

Answer strictly with JSON: {{&quot;task_success&quot;: true/false, &quot;reason&quot;: &quot;...&quot;, &quot;missing_resources&quot;: [...]}}&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing deterministic can check whether &quot;give me a VPC with a private subnet for the database&quot; produced the right resources. That is a semantic question and it needs a model. The point is not that LLM judges are useless. The point is that they should not be answering questions a compiler already answers.&lt;/p&gt;
&lt;h3 id=&quot;routing-the-retry&quot;&gt;Routing the retry&lt;/h3&gt;
&lt;p&gt;The evaluator's results feed a function that decides which domains re-run. Only the flagged ones:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;is_blocking = &quot;BLOCKING_ERROR&quot; in review_feedback
fmt_failed = eval_results.get(&quot;terraform_fmt_pass&quot;) is False
validate_failed = eval_results.get(&quot;terraform_validate_pass&quot;) is False

if not (is_blocking or fmt_failed or validate_failed):
    return []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things here. &lt;code&gt;is False&lt;/code&gt; and not falsy, so the &lt;code&gt;None&lt;/code&gt; case does not trigger a retry. And when something failed but no specific file was named in the output, every active domain retries rather than silently returning an empty list. A retry that does nothing is worse than an expensive one.&lt;/p&gt;
&lt;p&gt;The graph wires it as a conditional edge back to the subagents, capped at 3:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;workflow.add_conditional_edges(
    &quot;evaluator&quot;,
    should_retry_or_finish,
    {&quot;domain_subagents&quot;: &quot;domain_subagents&quot;, &quot;end&quot;: END},
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I would do differently&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;fmt&lt;/code&gt; output is a filename list, which makes attributing a failure to a domain easy. &lt;code&gt;validate&lt;/code&gt; output is prose from Terraform, and I am string-matching known filenames against it. That works until a filename appears in an error message for a reason unrelated to that file being broken. Parsing &lt;code&gt;terraform validate -json&lt;/code&gt; would give me structured diagnostics with real file and line attribution. I have not done it yet.&lt;/p&gt;
&lt;p&gt;The retry cap of 3 is a guess. I have no data on how often a third attempt fixes something a second one did not. That is a metric I should be emitting and am not.&lt;/p&gt;
&lt;p&gt;And the judge sees &lt;code&gt;json.dumps(aggregated_output)&lt;/code&gt; with the entire generated codebase inline. On a large request that is a lot of tokens for one boolean.&lt;/p&gt;
&lt;p&gt;94 tests pass on this. None of them mock &lt;code&gt;terraform&lt;/code&gt;, because the whole point was to stop trusting a stand-in for the real thing.&lt;/p&gt;
&lt;h3 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h3&gt;
&lt;p&gt;If a real tool can answer your evaluation question, run the real tool, and save the model for the questions that have no compiler.&lt;/p&gt;
&lt;p&gt;Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akashpersetti/terraform-agent&quot;&gt;github.com/akashpersetti/terraform-agent&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22978/i-ran-terraform-validate-inside-a-lambda-two-things-broke</guid>
<pubDate>Sat, 18 Jul 2026 20:32:28 +0000</pubDate>
</item>
<item>
<title>How to Start Docker for Beginners</title>
<link>https://coderlegion.com/22690/how-to-start-docker-for-beginners</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1t3vukgnuk2r4754axw6.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-s-docker&quot;&gt;What's Docker&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;Docker is a containerization platform that packages applications with all dependencies into portable utils called containers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;learn-the-foundational-concepts-of-docker&quot;&gt;Learn the foundational concepts of &lt;code&gt;Docker&lt;/code&gt;&lt;/h2&gt;
&lt;h3 id=&quot;core-concepts-&quot;&gt;Core concepts:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image&lt;/strong&gt;: A read-only blueprint containing app code,libraries,and environment settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container&lt;/strong&gt;: A running instance of an image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dockerfile&lt;/strong&gt;: Script defining how to build an image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume&lt;/strong&gt;: Persistent storage outside the container lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network&lt;/strong&gt;: Enables communication between containers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-building-and-running-a-container&quot;&gt;&lt;strong&gt;Example: Building and Running a Container&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;FROM node:18
WORKDIR /app
COPY package.json ./
RUN npm install
COPY .
EXPOSE 3000
CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t my-node-app

docker run -p 3000:3000 my-node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;managing-multi-container-apps-with-docker-compose&quot;&gt;Managing Multi-Container Apps with Docker Compose&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;version: '3'
services:
 app:
   build:
   ports:
     - &quot;3000:3000&quot;
   depends_on:
     - mongo
 mongo:
   image: mongo
   volumes:
     - mongo-data:/data/db
volumes:
 mongo-data:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RUN with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;best-practices&quot;&gt;Best Practices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;.dockerignore&lt;/code&gt; to exclude unnecessary files(e.g.,&lt;code&gt;.git&lt;/code&gt;,&lt;code&gt;node_modules&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Avoid &lt;code&gt;:latest&lt;/code&gt; tags;pin versions for stability.&lt;/li&gt;
&lt;li&gt;Run containers as non-root for security.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;multi-stage builds&lt;/strong&gt; to reduce image size.&lt;/li&gt;
&lt;li&gt;Store secrets in &lt;code&gt;.env&lt;/code&gt; files or Docker secrets, not in images.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-to-use-docker&quot;&gt;When to Use Docker&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[Y] Consistent dev/prod environments&lt;/li&gt;
&lt;li&gt;[N] Small static sites or quick scripts where setup overhead outweighs benefits.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Learn &lt;strong&gt;Docker networking&lt;/strong&gt;(bridge, host, overlay)for container communication.&lt;/li&gt;
&lt;li&gt;Explore &lt;strong&gt;Docker volumes&lt;/strong&gt; for persistent data.&lt;/li&gt;
&lt;li&gt;Integrate Docker into &lt;strong&gt;CI/CD pipelines&lt;/strong&gt; for automated builds and deployments.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;With these fundamentals, you can confidently containerize, run, and manage applications in any environment.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22690/how-to-start-docker-for-beginners</guid>
<pubDate>Wed, 15 Jul 2026 00:54:55 +0000</pubDate>
</item>
<item>
<title>Running Apache Airflow + Docker for Free Using GitHub Codespaces</title>
<link>https://coderlegion.com/21185/running-apache-airflow-docker-for-free-using-github-codespaces</link>
<description>&lt;p&gt;While building my ETL pipeline project, I ran into a common beginner problem:&lt;/p&gt;
&lt;p&gt;Running Apache Airflow locally on Windows with Docker was painful.&lt;/p&gt;
&lt;p&gt;Problems included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low disk space&lt;/li&gt;
&lt;li&gt;Docker setup issues&lt;/li&gt;
&lt;li&gt;Linux compatibility problems&lt;/li&gt;
&lt;li&gt;Environment debugging overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With only ~17GB free on my laptop, running multiple Airflow containers locally became difficult.&lt;/p&gt;
&lt;p&gt;So I moved the entire setup to GitHub Codespaces.&lt;/p&gt;
&lt;h3 id=&quot;what-codespaces-provided&quot;&gt;What Codespaces Provided&lt;/h3&gt;
&lt;p&gt;Out of the box:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu Linux environment&lt;/li&gt;
&lt;li&gt;Docker pre-installed&lt;/li&gt;
&lt;li&gt;VS Code in browser&lt;/li&gt;
&lt;li&gt;Auto-cloned GitHub repo&lt;/li&gt;
&lt;li&gt;Port forwarding for Airflow UI&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;workflow&quot;&gt;Workflow&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open Airflow UI&lt;/li&gt;
&lt;li&gt;Trigger ETL DAG&lt;/li&gt;
&lt;li&gt;Verify successful execution ✔️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Airflow was running in ~90 seconds.&lt;/p&gt;
&lt;h3 id=&quot;important-security-lesson&quot;&gt;Important Security Lesson&lt;/h3&gt;
&lt;p&gt;I accidentally committed my GCP service account key once.&lt;/p&gt;
&lt;p&gt;GitHub Secret Scanning blocked the push automatically.&lt;/p&gt;
&lt;p&gt;Immediately added:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gitignore&quot;&gt;*.json
.env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Never commit cloud credentials.&lt;/p&gt;
&lt;h3 id=&quot;why-this-setup-helps-beginners&quot;&gt;Why This Setup Helps Beginners&lt;/h3&gt;
&lt;p&gt;Codespaces removes a huge amount of local environment friction and lets you focus more on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Airflow orchestration&lt;/li&gt;
&lt;li&gt;ETL pipelines&lt;/li&gt;
&lt;li&gt;Docker workflows&lt;/li&gt;
&lt;li&gt;Cloud integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you'd like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beginner-friendly walkthrough → check Medium&lt;/li&gt;
&lt;li&gt;Engineering-focused breakdown → check Hashnode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/p/8832297e4d85/&quot;&gt;Link&lt;/a&gt;&lt;br&gt;
Hashnode: &lt;a rel=&quot;nofollow&quot; href=&quot;https://engineeringfriction.hashnode.dev/how-github-codespaces-helped-me-run-airflow-docker-free?utm_source=hashnode&amp;amp;utm_medium=feed&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Project Repo:&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tanmaybhurkunde/ETL-Pipeline&quot;&gt;ETL Pipeline&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;dataengineering-docker-apacheairflow-githubcodespaces&quot;&gt;DataEngineering #Docker #ApacheAirflow #GitHubCodespaces&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21185/running-apache-airflow-docker-for-free-using-github-codespaces</guid>
<pubDate>Tue, 23 Jun 2026 07:22:54 +0000</pubDate>
</item>
<item>
<title>What is Coolify? Self-Hosting with Superpowers</title>
<link>https://coderlegion.com/20869/what-is-coolify-self-hosting-with-superpowers</link>
<description>&lt;h2 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the last post, we talked about the VPS and why it is a compelling option for hosting your web applications. I mentioned a tool called Coolify that makes managing a VPS significantly easier. In this post, we are going to dive deeper into what Coolify actually is, what it does, and why I think it is one of the best tools available for developers and small teams who want the power of a VPS without the complexity of managing one from scratch.&lt;/p&gt;
&lt;h2 id=&quot;what-is-coolify-&quot;&gt;What is Coolify?&lt;/h2&gt;
&lt;p&gt;Coolify is a free, open-source, self-hostable platform as a service — or PaaS. Think of it as your own personal Heroku or Render, but running on your own server. This means you own your infrastructure, your data, and your costs.&lt;/p&gt;
&lt;p&gt;The best way to understand Coolify is to compare it to the alternatives. Platforms like Heroku, Render, and Railway are fully managed PaaS solutions. They abstract away all the server complexity — you push your code and it runs. The trade-off is cost and control. As your app scales, the bills grow quickly and you have limited control over the underlying infrastructure.&lt;/p&gt;
&lt;p&gt;Coolify gives you the same developer experience — push your code and it deploys — but on a VPS that you control. You get the simplicity of a managed platform with the economics and control of a VPS.&lt;/p&gt;
&lt;h2 id=&quot;what-does-coolify-do-&quot;&gt;What Does Coolify Do?&lt;/h2&gt;
&lt;p&gt;Coolify handles all the hard parts of running applications on a VPS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git Integration&lt;/strong&gt; -  Connect your GitHub, GitLab, or Bitbucket repository and Coolify will automatically deploy your app every time you push to your main branch. No manual deployments, no SSH commands — just push your code and it is live.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dockerized Deployments&lt;/strong&gt; -  Every application Coolify deploys runs in a Docker container. This means your apps are isolated, portable, and consistent across environments. You do not need to know Docker deeply to use Coolify — it handles the containerization for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic HTTPS&lt;/strong&gt; -  Coolify integrates with Let's Encrypt to automatically provision and renew SSL certificates for all your applications. Every app gets HTTPS out of the box with zero configuration on your part.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Built-in Reverse Proxy&lt;/strong&gt;  Coolify uses Traefik as its built-in reverse proxy and web server. It automatically routes traffic to the right application based on the domain name. You can run multiple applications on the same VPS and Coolify handles the routing between them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database Management&lt;/strong&gt; -  Coolify can deploy and manage databases alongside your applications — PostgreSQL, MySQL, MongoDB, Redis and more. You can spin up a database with a few clicks and connect it to your application without any manual configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables&lt;/strong&gt; -  Manage your environment variables securely through the Coolify dashboard. No more manually editing .env files on the server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring and Logs&lt;/strong&gt; -  Coolify provides basic monitoring and real-time log streaming for all your applications directly from the dashboard. You can see what your app is doing without SSH-ing into the server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backups&lt;/strong&gt; -  Coolify supports automated database backups to S3-compatible storage. Your data is protected without any manual backup scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-would-you-use-coolify-&quot;&gt;Why Would You Use Coolify?&lt;/h2&gt;
&lt;p&gt;There are several scenarios where Coolify makes a lot of sense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want the economics of a VPS without the complexity.&lt;/strong&gt; &lt;br&gt;
A 6USD to 10USD per month VPS with Coolify can run multiple applications that would cost hundreds of dollars per month on Heroku, Render, or Railway. For a startup or indie developer this is a significant saving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want full control over your infrastructure.&lt;/strong&gt;&lt;br&gt;
With Coolify you own everything. Your data stays on your server. You choose your hosting provider. You are not locked into any platform's pricing or terms of service.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want a great developer experience.&lt;/strong&gt;&lt;br&gt;
Coolify's dashboard is clean and intuitive. Deploying an application is genuinely just a few clicks. It does not feel like managing a server — it feels like using a modern PaaS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You are running multiple projects.&lt;/strong&gt; &lt;br&gt;
One VPS with Coolify can host multiple applications, multiple databases, and multiple domains. Instead of paying for separate hosting for each project, you consolidate everything onto one server.&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-limitations-&quot;&gt;What Are the Limitations?&lt;/h2&gt;
&lt;p&gt;Coolify is not perfect for every situation. Here are some things to be aware of.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You are responsible for your server&lt;/strong&gt; — if your VPS goes down, your apps go down. With managed platforms the provider handles up time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some configuration is still required&lt;/strong&gt; — especially for custom setups, firewalls, and advanced networking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It is self-hosted&lt;/strong&gt; — meaning you need to keep Coolify itself updated and maintained.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for very large scale&lt;/strong&gt; — for enterprise applications with massive traffic you may need dedicated infrastructure beyond a single VPS.&lt;/p&gt;
&lt;h2 id=&quot;how-do-you-get-started-with-coolify-&quot;&gt;How Do You Get Started with Coolify?&lt;/h2&gt;
&lt;p&gt;Getting Coolify up and running is surprisingly straightforward. In an upcoming post I will walk you through the complete setup — from provisioning a VPS to having Coolify installed and your first application deployed.&lt;/p&gt;
&lt;p&gt;All you need to get started is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;• A VPS with at least 2GB RAM and 2 CPU cores 
• A domain name 
• About 30 minutes of your time 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Coolify bridges the gap between the simplicity of managed platforms and the power and economics of a VPS. For developers and small teams who want to own their infrastructure without being overwhelmed by server management, it is genuinely one of the best tools available right now.&lt;/p&gt;
&lt;p&gt;In an upcoming post we will get our hands dirty and set up Coolify from scratch. See you there.&lt;/p&gt;
&lt;p&gt;References&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;• https://coolify.io 
• https://coolify.io/docs 
• https://github.com/coollabsio/coolify
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20869/what-is-coolify-self-hosting-with-superpowers</guid>
<pubDate>Thu, 18 Jun 2026 03:34:08 +0000</pubDate>
</item>
<item>
<title>The Pipeline Was Green for Three Weeks. It Had Been Shipping a Build That Never Compiled.</title>
<link>https://coderlegion.com/20433/the-pipeline-was-green-for-three-weeks-it-had-been-shipping-a-build-that-never-compiled</link>
<description>&lt;p&gt;For three weeks a deployment pipeline reported every step green and shipped a build that had failed to compile on every single run. The build step ended in &lt;code&gt;npm run build | tee build.log&lt;/code&gt; so the output could be archived. That pipe is the whole story: bash returns the exit status of the &lt;em&gt;last&lt;/em&gt; command in a pipeline, which was &lt;code&gt;tee&lt;/code&gt;, and &lt;code&gt;tee&lt;/code&gt; always succeeds at copying text. The compiler's non-zero exit got thrown away the instant the pipe handed off. The error was sitting right there in &lt;code&gt;build.log&lt;/code&gt;. GitHub Actions saw exit code 0, painted the step green, and deployed the broken artifact. Nobody read the log, because the checkmark said there was nothing to read.&lt;/p&gt;
&lt;p&gt;That's the defining property of bash in CI, and it's why I treat pipeline scripts differently from anything I run in a terminal: &lt;strong&gt;a silent failure can present as success.&lt;/strong&gt; On a server you watch a command fail in front of you. In a pipeline, a swallowed exit code produces a green checkmark over broken code, and the gap between &quot;the logs show an error&quot; and &quot;the pipeline reports failure&quot; is exactly where outages are born. I wrote the full guide because I've now been burned by every variation of this, and there's a consistent set of habits that close the gap.&lt;/p&gt;
&lt;p&gt;There are four failure modes that are specific to CI and barely ever bite you at an interactive prompt. &lt;strong&gt;Exit codes swallowed by a pipe&lt;/strong&gt; — the story above, any &lt;code&gt;command | tee&lt;/code&gt;, &lt;code&gt;command | grep&lt;/code&gt;, &lt;code&gt;command | sort&lt;/code&gt;. &lt;strong&gt;Shell provisioning differences&lt;/strong&gt; — &lt;code&gt;ubuntu-latest&lt;/code&gt; gives you bash 5.x, &lt;code&gt;macos-latest&lt;/code&gt; gives you bash 3.2 from 2007, and a script using associative arrays or &lt;code&gt;${var,,}&lt;/code&gt; passes on one runner and throws a syntax error on the other in the same workflow. &lt;strong&gt;Environment variable gaps&lt;/strong&gt; — CI sets variables you don't control and omits ones you assume exist, and without &lt;code&gt;set -u&lt;/code&gt; a missing &lt;code&gt;$DEPLOY_TARGET&lt;/code&gt; becomes an empty string and does something quietly wrong. &lt;strong&gt;Interactive-shell assumptions&lt;/strong&gt; — CI runs a non-interactive, non-login shell that never sources your &lt;code&gt;.bashrc&lt;/code&gt;, so a command that works when you type it dies with &lt;code&gt;command not found&lt;/code&gt; because the thing that defined it was never loaded.&lt;/p&gt;
&lt;p&gt;The header that closes most of these is short, and every line earns its place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set -e&lt;/code&gt; exits the moment any command fails, so the step exits non-zero and the workflow actually registers a failure. &lt;code&gt;set -u&lt;/code&gt; treats an unset variable as an error, so a typo'd &lt;code&gt;$DPLOY_TARGET&lt;/code&gt; dies immediately instead of expanding to nothing and corrupting a path. &lt;code&gt;set -o pipefail&lt;/code&gt; makes a pipeline return the first non-zero exit among its commands rather than only the last — that one flag is the direct fix for the &lt;code&gt;| tee&lt;/code&gt; bug that ran green for three weeks.&lt;/p&gt;
&lt;p&gt;Secrets deserve their own paragraph because CI hands you &lt;code&gt;env:&lt;/code&gt; values and &lt;code&gt;secrets:&lt;/code&gt; values identically — the shell can't tell them apart, the only difference is that Actions masks the secret's literal string in the log. The trap is that the moment you transform a secret (base64-decode it, slice it, interpolate it), the transformed value no longer matches the mask and prints in clear text. Validate required values up front with &lt;code&gt;${VAR:?}&lt;/code&gt; so a missing secret fails at startup with a clear message instead of on line 47 with a cryptic &lt;code&gt;permission denied&lt;/code&gt;, and be very careful with &lt;code&gt;set -x&lt;/code&gt; in any step that touches a secret.&lt;/p&gt;
&lt;p&gt;The pipe-exit-code problem is worth one concrete tool beyond &lt;code&gt;pipefail&lt;/code&gt;: &lt;code&gt;PIPESTATUS&lt;/code&gt; is an array holding the exit code of every command in the last pipeline, read immediately after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build | tee build.log
build_rc=&quot;${PIPESTATUS[0]}&quot;   # npm's code, not tee's
[[ &quot;$build_rc&quot; -eq 0 ]] || { echo &quot;build failed: $build_rc&quot; &amp;gt;&amp;amp;2; exit &quot;$build_rc&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pipefail&lt;/code&gt; has one well-known false positive — &lt;code&gt;grep&lt;/code&gt; returns 1 when it finds no matches, which is often fine, and under &lt;code&gt;pipefail&lt;/code&gt; plus &lt;code&gt;set -e&lt;/code&gt; that aborts the script. Absorb it deliberately with &lt;code&gt;|| true&lt;/code&gt; only where a non-match is genuinely acceptable, and nowhere else, because blanketing every command in &lt;code&gt;|| true&lt;/code&gt; just reinvents the silent-success problem you're trying to kill.&lt;/p&gt;
&lt;p&gt;Docker has its own landmine: every entrypoint script must end with &lt;code&gt;exec &quot;$@&quot;&lt;/code&gt;. Without it, your script stays PID 1 and your app runs as a child, so when the orchestrator sends SIGTERM on &lt;code&gt;docker stop&lt;/code&gt; or a rolling deploy, the signal hits the &lt;em&gt;script&lt;/em&gt;, which doesn't forward it, and after the grace period the orchestrator escalates to SIGKILL — abrupt termination, dropped connections, lost in-flight work. &lt;code&gt;exec &quot;$@&quot;&lt;/code&gt; replaces the shell with your app so it &lt;em&gt;becomes&lt;/em&gt; PID 1 and receives signals directly. The guide pairs this with a &lt;code&gt;wait_for&lt;/code&gt; dependency-check pattern and a trap, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/bash-trap-builder&quot;&gt;Bash trap &amp;amp; Signal Handler Builder&lt;/a&gt; generates the exact signal block an entrypoint needs.&lt;/p&gt;
&lt;p&gt;Deploys get the same treatment: deploy into a fresh timestamped directory, flip a &lt;code&gt;current&lt;/code&gt; symlink atomically with &lt;code&gt;ln -sfn&lt;/code&gt; so traffic never sees a half-written release, keep the last several releases so rollback is just re-pointing the symlink, run a health check after the swap and fail the deploy if it doesn't pass, and stamp the git SHA into the release so &quot;what's running right now&quot; always has an answer. And when a step fails and the logs won't say why, &lt;code&gt;set -x&lt;/code&gt; around just the suspect section shows you each command with its variables expanded — a doubled slash or an empty segment in the trace is usually your bug standing in plain sight.&lt;/p&gt;
&lt;p&gt;The full guide is the field manual version of all of this — the four failure modes, the safe header, secret validation with a &lt;code&gt;validate_env&lt;/code&gt; function, &lt;code&gt;PIPESTATUS&lt;/code&gt; and &lt;code&gt;pipefail&lt;/code&gt;, Docker entrypoints, the atomic-symlink deploy script with rollback and health check, debugging with &lt;code&gt;set -x&lt;/code&gt;, and a production-ready checklist at the end: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/guides/bash-scripting-for-ci-cd-pipelines&quot;&gt;https://bashsnippets.xyz/guides/bash-scripting-for-ci-cd-pipelines&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If your pipeline scripts are dying on the small stuff first — unquoted loops, bad argument parsing, missing traps — the snippet library that feeds into this guide is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz&quot;&gt;https://bashsnippets.xyz&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20433/the-pipeline-was-green-for-three-weeks-it-had-been-shipping-a-build-that-never-compiled</guid>
<pubDate>Fri, 12 Jun 2026 16:25:56 +0000</pubDate>
</item>
<item>
<title>Why Every Developer Should Learn Docker in 2026</title>
<link>https://coderlegion.com/20419/why-every-developer-should-learn-docker-in-2026</link>
<description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modern software development has changed dramatically over the last decade. Applications are no longer deployed on a single server with manual configuration. Today, developers build cloud-native applications, deploy microservices, and work across multiple environments. In this landscape, Docker has become one of the most important tools a developer can learn.&lt;/p&gt;
&lt;p&gt;Whether you're a backend developer, frontend engineer, DevOps professional, or software architect, Docker provides a consistent way to build, package, and deploy applications. In 2026, Docker knowledge is no longer just a bonus skill—it's quickly becoming a requirement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is Docker?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Docker is a containerization platform that allows developers to package an application along with all its dependencies into a lightweight, portable container. These containers can run consistently across different environments, including local machines, testing servers, and production infrastructure.&lt;/p&gt;
&lt;p&gt;Instead of worrying about operating system differences or dependency conflicts, developers can create a container once and run it anywhere.&lt;/p&gt;
&lt;p&gt;Why Developers Love Docker&lt;br&gt;
Eliminates &quot;Works on My Machine&quot; Problems&lt;/p&gt;
&lt;p&gt;One of the biggest frustrations in software development occurs when code runs perfectly on one machine but fails on another.&lt;/p&gt;
&lt;p&gt;Docker solves this by creating isolated environments that contain everything the application needs. Every team member works with the same configuration, reducing environment-related bugs.&lt;/p&gt;
&lt;p&gt; Faster Development and Testing&lt;/p&gt;
&lt;p&gt;Developers can spin up databases, APIs, caching systems, and entire application stacks with a few commands.&lt;/p&gt;
&lt;p&gt;Instead of manually installing PostgreSQL, Redis, or MongoDB, developers can simply launch containers and begin working immediately.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Simplified Deployment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Docker makes deployments predictable. Since the same container used during development is deployed to production, the chances of unexpected behavior are significantly reduced.&lt;/p&gt;
&lt;p&gt;This consistency improves reliability and speeds up release cycles.&lt;/p&gt;
&lt;p&gt; Essential for Cloud-Native Applications&lt;/p&gt;
&lt;p&gt;Most modern cloud platforms and orchestration systems, including Kubernetes, rely heavily on containerized workloads.&lt;/p&gt;
&lt;p&gt;Learning Docker provides the foundation for understanding scalable cloud infrastructure and DevOps practices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker and CI/CD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Continuous Integration and Continuous Deployment pipelines have become standard in software engineering.&lt;/p&gt;
&lt;p&gt;Docker integrates seamlessly with CI/CD tools such as GitHub Actions, GitLab CI, Azure DevOps, and Jenkins. Containers allow automated pipelines to build, test, and deploy applications consistently across environments.&lt;/p&gt;
&lt;p&gt;Organizations increasingly expect developers to understand how containers fit into automated deployment workflows.&lt;/p&gt;
&lt;p&gt;Real-World Use Cases&lt;/p&gt;
&lt;p&gt;Docker is used by companies of all sizes for:&lt;/p&gt;
&lt;p&gt;Microservices architecture&lt;br&gt;
API deployment&lt;br&gt;
Machine learning applications&lt;br&gt;
Web development environments&lt;br&gt;
Automated testing&lt;br&gt;
Continuous deployment pipelines&lt;br&gt;
Cloud-native infrastructure&lt;/p&gt;
&lt;p&gt;From startups to enterprise organizations, containerization has become a standard practice.&lt;/p&gt;
&lt;p&gt;Skills Developers Should Learn&lt;/p&gt;
&lt;p&gt;To become productive with Docker, focus on:&lt;/p&gt;
&lt;p&gt;Docker Images&lt;br&gt;
Docker Containers&lt;br&gt;
Dockerfiles&lt;br&gt;
Docker Compose&lt;br&gt;
Container Networking&lt;br&gt;
Volumes and Persistent Storage&lt;br&gt;
Container Security&lt;br&gt;
Basic Kubernetes Concepts&lt;/p&gt;
&lt;p&gt;Mastering these topics provides a strong foundation for modern software deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Docker has transformed how applications are developed and deployed. It improves consistency, simplifies collaboration, and enables scalable cloud-native architectures.&lt;/p&gt;
&lt;p&gt;As organizations continue adopting containers and Kubernetes, Docker remains one of the most valuable skills a developer can invest in. Learning Docker today not only improves your workflow but also opens doors to DevOps, cloud engineering, and modern software architecture opportunities.&lt;/p&gt;
&lt;p&gt;In 2026, developers who understand containers are better equipped to build, deploy, and scale software efficiently. The sooner you learn Docker, the more prepared you'll be for the future of software development.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20419/why-every-developer-should-learn-docker-in-2026</guid>
<pubDate>Fri, 12 Jun 2026 13:39:15 +0000</pubDate>
</item>
<item>
<title>From Dockerfile to Docker Hub: A Complete Beginner's Guide to Docker </title>
<link>https://coderlegion.com/20281/from-dockerfile-to-docker-hub-a-complete-beginners-guide-to-docker</link>
<description>&lt;p&gt;Containerization has become a fundamental skill for DevOps Engineers, Cloud Engineers, and Software Developers.&lt;/p&gt;
&lt;p&gt;However, many beginners learn Docker commands without understanding the complete workflow behind them.&lt;/p&gt;
&lt;p&gt;Questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly is a Docker Image?&lt;/li&gt;
&lt;li&gt;How does a Dockerfile become a running Container?&lt;/li&gt;
&lt;li&gt;Why do we push images to Docker Hub?&lt;/li&gt;
&lt;li&gt;How do organizations deploy the same application everywhere?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;are common when starting out.&lt;/p&gt;
&lt;p&gt;In this blog, we'll build a simple Node.js application, containerize it using Docker, run it locally, and finally push the image to Docker Hub.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll understand the complete Docker workflow used in real-world DevOps environments.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;docker-workflow-overview&quot;&gt;Docker Workflow Overview&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Application Code] --&amp;gt; B[Dockerfile]
    B --&amp;gt; C[Docker Build]
    C --&amp;gt; D[Docker Image]
    D --&amp;gt; E[Docker Run]
    E --&amp;gt; F[Docker Container]
    D --&amp;gt; G[Docker Push]
    G --&amp;gt; H[Docker Hub]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same workflow followed by developers and DevOps teams worldwide.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-are-we-building-&quot;&gt;What Are We Building?&lt;/h2&gt;
&lt;p&gt;We'll create a simple Node.js application that returns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A welcome message&lt;/li&gt;
&lt;li&gt;Environment information&lt;/li&gt;
&lt;li&gt;Container hostname&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This helps us verify that our application is actually running inside a Docker container.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.
├── Dockerfile
├── app.js
└── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-1-create-the-node-js-application&quot;&gt;Step 1: Create the Node.js Application&lt;/h2&gt;
&lt;h3 id=&quot;app-js&quot;&gt;app.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require(&quot;express&quot;);
const app = express();

const PORT = process.env.PORT || 3000;
const ENV = process.env.ENV_VALUE || &quot;No env set&quot;;
const HOSTNAME = process.env.HOSTNAME || require(&quot;os&quot;).hostname();

app.get(&quot;/&quot;, (req, res) =&amp;gt; {
  res.json({
    message: &quot;Hello from Simple App (Node)&quot;,
    env: ENV,
    container: HOSTNAME
  });
});

app.listen(PORT, () =&amp;gt;
  console.log(`Node Hello listening on ${PORT}`)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;package-json&quot;&gt;package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;hello-app-node&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;app.js&quot;,
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^5.2.1&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;application-architecture&quot;&gt;Application Architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    User --&amp;gt; Browser
    Browser --&amp;gt; Container
    Container --&amp;gt; NodeJS
    NodeJS --&amp;gt; JSONResponse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever a user accesses the application, the request reaches the containerized Node.js application, which returns a JSON response.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-create-the-dockerfile&quot;&gt;Step 2: Create the Dockerfile&lt;/h2&gt;
&lt;p&gt;A Dockerfile is simply a blueprint that tells Docker how to build an image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:24-slim

WORKDIR /app

COPY package*.json ./

RUN npm install --production

COPY . .

EXPOSE 3000

CMD [&quot;node&quot;, &quot;app.js&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-the-dockerfile&quot;&gt;Understanding the Dockerfile&lt;/h3&gt;
&lt;h4 id=&quot;base-image&quot;&gt;Base Image&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM node:24-slim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We start with an official lightweight Node.js image from Docker Hub.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;working-directory&quot;&gt;Working Directory&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;WORKDIR /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creates and switches to the &lt;code&gt;/app&lt;/code&gt; directory inside the container.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;copy-dependency-files&quot;&gt;Copy Dependency Files&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY package*.json ./
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copies package files first.&lt;/p&gt;
&lt;p&gt;This is considered a Docker best practice because Docker can cache dependency layers and speed up future builds.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;install-dependencies&quot;&gt;Install Dependencies&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;RUN npm install --production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Installs only production dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;copy-application-source-code&quot;&gt;Copy Application Source Code&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;COPY . .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copies the entire project into the container.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;expose-application-port&quot;&gt;Expose Application Port&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Documents that the application listens on port 3000.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;start-the-application&quot;&gt;Start the Application&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;CMD [&quot;node&quot;, &quot;app.js&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Runs the Node.js application when the container starts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-happens-during-docker-build-&quot;&gt;What Happens During Docker Build?&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    A[Dockerfile]
    --&amp;gt; B[Pull Node Base Image]
    --&amp;gt; C[Create Working Directory]
    --&amp;gt; D[Copy package.json]
    --&amp;gt; E[Install Dependencies]
    --&amp;gt; F[Copy Application Code]
    --&amp;gt; G[Create Image Layers]
    --&amp;gt; H[Docker Image Ready]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker executes each instruction sequentially and creates image layers.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-3-build-the-docker-image&quot;&gt;Step 3: Build the Docker Image&lt;/h2&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t madhavnaks/node-app .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's understand this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Builds an image from a Dockerfile.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;-t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Assigns a tag to the image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Image name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Current directory containing the Dockerfile.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;After a successful build you'll see output similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;=&amp;gt; naming to docker.io/madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker has now created an image locally on your machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-4-verify-the-image&quot;&gt;Step 4: Verify the Image&lt;/h2&gt;
&lt;p&gt;To see locally available images:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;REPOSITORY                TAG       IMAGE ID       SIZE
madhavnaks/node-app       latest    42c936686000   234MB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, the image exists but is not running.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;docker-image-vs-docker-container&quot;&gt;Docker Image vs Docker Container&lt;/h2&gt;
&lt;p&gt;Many beginners confuse these two concepts.&lt;/p&gt;
&lt;p&gt;Think of it like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker Image = Blueprint&lt;/li&gt;
&lt;li&gt;Docker Container = Running Application&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Docker Image]
    --&amp;gt; B[Container 1]

    A --&amp;gt; C[Container 2]

    A --&amp;gt; D[Container 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A single image can create multiple containers.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-5-run-a-container&quot;&gt;Step 5: Run a Container&lt;/h2&gt;
&lt;p&gt;Now let's start our application.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d --name node-app -p 3001:3000 madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-the-command&quot;&gt;Understanding the Command&lt;/h3&gt;
&lt;h4 id=&quot;detached-mode&quot;&gt;Detached Mode&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;-d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Runs the container in the background.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;container-name&quot;&gt;Container Name&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;--name node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Assigns a friendly name to the container.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;port-mapping&quot;&gt;Port Mapping&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;-p 3001:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Host Port      → Container Port
3001           → 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application listens on port 3000 inside the container but is accessible on port 3001 from your machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-6-verify-the-container&quot;&gt;Step 6: Verify the Container&lt;/h2&gt;
&lt;p&gt;Check running containers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;CONTAINER ID   IMAGE                 STATUS
10265698c978   madhavnaks/node-app   Up
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-7-access-the-application&quot;&gt;Step 7: Access the Application&lt;/h2&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:3001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;message&quot;: &quot;Hello from Simple App (Node)&quot;,
  &quot;env&quot;: &quot;No env set&quot;,
  &quot;container&quot;: &quot;10265698c978&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the hostname.&lt;/p&gt;
&lt;p&gt;The hostname corresponds to the container ID, proving the application is running inside Docker.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-8-login-to-docker-hub&quot;&gt;Step 8: Login to Docker Hub&lt;/h2&gt;
&lt;p&gt;Before pushing images, authenticate with Docker Hub.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Login Succeeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker is now authorized to push images to your repository.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-push-images-to-docker-hub-&quot;&gt;Why Push Images to Docker Hub?&lt;/h2&gt;
&lt;p&gt;Docker Hub acts as a centralized registry.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Developer Machine]
    --&amp;gt;|Push| B[Docker Hub]

    B --&amp;gt;|Pull| C[Server]

    B --&amp;gt;|Pull| D[Kubernetes Cluster]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once an image is pushed, it can be pulled and run from anywhere.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-9-push-the-image&quot;&gt;Step 9: Push the Image&lt;/h2&gt;
&lt;p&gt;Push the image to Docker Hub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker push madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;latest: digest: sha256:d1081ec542f521e100bd886943c13ab7568c663b56e36200e6d846163975f979
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirms that the image has been successfully uploaded.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-10-verify-on-docker-hub&quot;&gt;Step 10: Verify on Docker Hub&lt;/h2&gt;
&lt;p&gt;Visit your Docker Hub account.&lt;/p&gt;
&lt;p&gt;You should now see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;listed under repositories.&lt;/p&gt;
&lt;p&gt;You can even share the image with others.&lt;/p&gt;
&lt;p&gt;Anyone can now run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker pull madhavnaks/node-app

docker run -p 3001:3000 madhavnaks/node-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;without needing your source code.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-devops-workflow&quot;&gt;Real-World DevOps Workflow&lt;/h2&gt;
&lt;p&gt;In production environments, the workflow usually looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Developer Writes Code]
    --&amp;gt; B[GitHub]

    B --&amp;gt; C[CI/CD Pipeline]

    C --&amp;gt; D[Docker Build]

    D --&amp;gt; E[Docker Hub]

    E --&amp;gt; F[Kubernetes Cluster]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is exactly how modern DevOps teams package and deploy applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;key-takeaways-1&quot;&gt;Key Takeaways&lt;/h2&gt;
&lt;p&gt;✅ Dockerfile defines how an image should be built&lt;/p&gt;
&lt;p&gt;✅ Docker Build creates an image&lt;/p&gt;
&lt;p&gt;✅ Docker Run creates a container&lt;/p&gt;
&lt;p&gt;✅ Docker Hub stores and distributes images&lt;/p&gt;
&lt;p&gt;✅ One image can create multiple containers&lt;/p&gt;
&lt;p&gt;✅ Containerization ensures consistency across environments&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this guide, we covered the complete Docker workflow from source code to Docker Hub.&lt;/p&gt;
&lt;p&gt;We learned how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a Dockerfile&lt;/li&gt;
&lt;li&gt;Build a Docker image&lt;/li&gt;
&lt;li&gt;Run a container&lt;/li&gt;
&lt;li&gt;Verify application execution&lt;/li&gt;
&lt;li&gt;Push images to Docker Hub&lt;/li&gt;
&lt;li&gt;Understand how images move through real-world DevOps pipelines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mastering these Docker fundamentals is the first step toward advanced topics like Docker Compose, Kubernetes, Helm, CI/CD pipelines, and cloud-native deployments.&lt;/p&gt;
&lt;p&gt;If you're learning DevOps, try containerizing applications in different languages such as Python, Java, Go, or .NET to strengthen your understanding of Docker.&lt;/p&gt;
&lt;p&gt;Happy Containerizing! &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20281/from-dockerfile-to-docker-hub-a-complete-beginners-guide-to-docker</guid>
<pubDate>Thu, 11 Jun 2026 10:31:28 +0000</pubDate>
</item>
<item>
<title>How I run LocalStack without committing LOCALSTACK_AUTH_TOKEN</title>
<link>https://coderlegion.com/20078/how-i-run-localstack-without-committing-localstackauthtoken</link>
<description>&lt;h3 id=&quot;using-envilder-to-keep-the-localstack-auth-token-out-of-git-while-preserving-a-reproducible-local-docker-compose-setup-&quot;&gt;&lt;em&gt;Using &lt;a rel=&quot;nofollow&quot; href=&quot;https://envilder.com/&quot;&gt;Envilder&lt;/a&gt; to keep the LocalStack auth token out of Git while preserving a reproducible local Docker Compose setup.&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;I wanted a reproducible LocalStack setup without committing &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt; to the repository.&lt;/p&gt;
&lt;p&gt;That sounds simple, but it touches a common local development problem:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;How do you make a project easy to run without putting real secrets in Git?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LocalStack &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.localstack.cloud/aws/getting-started/auth-token/&quot;&gt;expects the auth token&lt;/a&gt; to be available through the &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt; environment variable when starting it with Docker, Docker Compose, the LocalStack CLI, or CI workflows.&lt;/p&gt;
&lt;p&gt;Since &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.localstack.cloud/localstack-for-aws-release-2026-03-0/&quot;&gt;LocalStack for AWS 2026.03.0&lt;/a&gt;, it ships as a consolidated Docker image and requires an auth token to start, so managing &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt; cleanly is now part of the normal setup path, not just a Pro-only edge case.&lt;/p&gt;
&lt;p&gt;That token is a secret, so I did not want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;commit it to the repository&lt;/li&gt;
&lt;li&gt;paste it into &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;duplicate it across helper scripts&lt;/li&gt;
&lt;li&gt;rely on a manual &lt;code&gt;export LOCALSTACK_AUTH_TOKEN=...&lt;/code&gt; step every time&lt;/li&gt;
&lt;li&gt;make the setup work only on my machine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I wanted instead was a small, repeatable contract:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;this project needs LOCALSTACK_AUTH_TOKEN
resolve it from the secret store
inject it locally when needed
do not commit the value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the pattern I ended up using with &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/macalbert/envilder&quot;&gt;Envilder&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-pattern&quot;&gt;The Pattern&lt;/h3&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;secret values stay in a real secret store&lt;/li&gt;
&lt;li&gt;the repository contains only the mapping&lt;/li&gt;
&lt;li&gt;local development generates a &lt;code&gt;.env&lt;/code&gt; file from that mapping&lt;/li&gt;
&lt;li&gt;Docker Compose reads the generated &lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the token never gets committed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, the secret is &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In another project, it could be a database password, webhook secret, API key, or service credential.&lt;/p&gt;
&lt;h3 id=&quot;defining-the-secret-mapping&quot;&gt;Defining the Secret Mapping&lt;/h3&gt;
&lt;p&gt;Here is a minimal &lt;code&gt;envilder.json&lt;/code&gt; example using AWS SSM Parameter Store:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;$schema&quot;: &quot;https://envilder.com/schema/map-file.v1.json&quot;,
  &quot;$config&quot;: {
    &quot;provider&quot;: &quot;aws&quot;,
    &quot;profile&quot;: &quot;default&quot;
  },
  &quot;LOCALSTACK_AUTH_TOKEN&quot;: &quot;/envilder/localstack/auth-token&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is safe to commit because it does not contain the token.&lt;/p&gt;
&lt;p&gt;It only says:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;_To produce &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt;, resolve this value from the secret store._&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The actual token remains outside the repository.&lt;/p&gt;
&lt;h3 id=&quot;generating-the-local-env&quot;&gt;Generating the Local &lt;code&gt;.env&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Before starting LocalStack, generate the local environment file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx envilder --map=envilder.json --envfile=.env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That produces a local &lt;code&gt;.env&lt;/code&gt; file like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;LOCALSTACK_AUTH_TOKEN=...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file should be ignored by Git:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gitignore&quot;&gt;.env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the project contains the setup contract, but not the secret.&lt;/p&gt;
&lt;h3 id=&quot;passing-the-token-to-localstack&quot;&gt;Passing the Token to LocalStack&lt;/h3&gt;
&lt;p&gt;A Docker Compose service can reference the variable as usual:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  localstack:
    image: localstack/localstack:stable
    ports:
      - &quot;4566:4566&quot;
      - &quot;4510-4559:4510-4559&quot;
    environment:
      LOCALSTACK_AUTH_TOKEN: \$&quot;{LOCALSTACK_AUTH_TOKEN:?Run npx envilder first}&quot;
    volumes:
      - &quot;/var/run/docker.sock:/var/run/docker.sock&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker Compose automatically reads a &lt;code&gt;.env&lt;/code&gt; file from the project directory for variable interpolation.&lt;/p&gt;
&lt;p&gt;If your generated env file lives somewhere else, you can pass it explicitly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose --env-file path/to/.env up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The payoff is what a new contributor sees after cloning:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx envilder --map=envilder.json --envfile=.env
docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two commands, no Slack message asking &quot;where do I get the token?&quot;, no secret in Git.&lt;/p&gt;
&lt;h3 id=&quot;why-not-direnv-1password-or-chamber-&quot;&gt;Why not direnv, 1Password, or chamber?&lt;/h3&gt;
&lt;p&gt;For one developer, &lt;code&gt;export LOCALSTACK_AUTH_TOKEN=...&lt;/code&gt; works, but it creates hidden local state the repo never documents. The interesting comparison is against the tools that already resolve secrets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;direnv&lt;/strong&gt; loads env vars when you enter a directory, but &lt;code&gt;.envrc&lt;/code&gt; is just shell. You still write &lt;code&gt;export LOCALSTACK_AUTH_TOKEN=$(aws ssm get-parameter ...)&lt;/code&gt; yourself, per variable, per project. It is a loader, not a resolver, and the mapping lives in bespoke shell instead of a declared contract.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1Password &lt;code&gt;op run&lt;/code&gt;&lt;/strong&gt; injects secrets at runtime and is excellent, but it assumes 1Password is your store. If your secrets already live in AWS SSM or Azure Key Vault, you are adding a vendor, not removing one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;chamber&lt;/strong&gt; is the closest match for the AWS-only case:&lt;br&gt;
&lt;code&gt;chamber exec service -- docker compose up&lt;/code&gt; reads straight from SSM. If you never leave AWS and only need secrets at the command line, chamber or a three-line script is genuinely enough.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Envilder's narrow bet is different on three points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The mapping is an explicit &lt;code&gt;name -&amp;gt; path&lt;/code&gt; contract committed as JSON, not a service prefix or shell glue.&lt;/li&gt;
&lt;li&gt;The store is your own cloud. No third-party infrastructure holds the secret.&lt;/li&gt;
&lt;li&gt;The same map drives the CLI here, CI, and in-process resolution through the SDKs, so local dev and runtime read one contract.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you only ever touch AWS from a shell, you may not need it. The case shows up when you have more than one provider, more than one language, or want the same declared contract locally, in CI, and at runtime.&lt;/p&gt;
&lt;h3 id=&quot;the-slightly-meta-part&quot;&gt;The Slightly Meta Part&lt;/h3&gt;
&lt;p&gt;I ran into this while working on Envilder.&lt;/p&gt;
&lt;p&gt;Envilder uses LocalStack for integration testing.&lt;/p&gt;
&lt;p&gt;LocalStack needs &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So I used Envilder to inject the token needed to start the LocalStack environment used to test Envilder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tfkdag8d7ske6xb59ta.png&quot; alt=&quot;Envilder uses Envilder to inject LocalStack token&quot;&gt;&lt;/p&gt;
&lt;p&gt;The tool became its own first customer, which is always a useful kind of feedback.&lt;/p&gt;
&lt;h3 id=&quot;a-note-on-localstack-auth-tokens&quot;&gt;A Note on LocalStack Auth Tokens&lt;/h3&gt;
&lt;p&gt;LocalStack's documentation is clear that &lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt; should be kept confidential and should not be committed to source control.&lt;/p&gt;
&lt;p&gt;This post is not about bypassing that requirement.&lt;/p&gt;
&lt;p&gt;It is about treating the token like any other secret:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;store it safely&lt;/li&gt;
&lt;li&gt;resolve it when needed&lt;/li&gt;
&lt;li&gt;avoid leaking it into Git&lt;/li&gt;
&lt;li&gt;keep the project setup reproducible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For open source maintainers, this matters. A project can require credentials for local or CI workflows and still provide a clean setup path for contributors.&lt;/p&gt;
&lt;h3 id=&quot;takeaway-1&quot;&gt;Takeaway&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;LOCALSTACK_AUTH_TOKEN&lt;/code&gt; is just one example of a broader problem.&lt;/p&gt;
&lt;p&gt;Projects often need secrets during local development, CI/CD, tests, and runtime. The hard part is not only storing those secrets safely. It is keeping the mapping between &quot;what the app needs&quot; and &quot;where the secret lives&quot; clear and consistent.&lt;/p&gt;
&lt;p&gt;The pattern I like is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;secret values outside Git
secret mappings inside Git
one contract for local dev, CI/CD, and runtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the reason I am building Envilder.&lt;/p&gt;
&lt;p&gt;Not to add another place to store secrets.&lt;/p&gt;
&lt;p&gt;Just to make secret resolution explicit, repeatable, and versionable.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20078/how-i-run-localstack-without-committing-localstackauthtoken</guid>
<pubDate>Tue, 09 Jun 2026 14:51:24 +0000</pubDate>
</item>
<item>
<title>Running Astro in a preview container</title>
<link>https://coderlegion.com/19983/running-astro-in-a-preview-container</link>
<description>&lt;p&gt;If you've ever worked on a collection of different Node apps, you've likely encountered version conflicts. Everyone wants a different version of Node or PNPM, and your new job is trying to align them all, or managing versions daily.&lt;/p&gt;
&lt;p&gt;That's when open-source hero &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/kostiskapelonis/&quot;&gt;Kostis Kapelonis&lt;/a&gt; said, &quot;Why don't we run the preview in a container?&quot; In fact, he didn't just say this; he also submitted a PR. I told you he's an open-source hero.&lt;/p&gt;
&lt;p&gt;The PR added a &lt;code&gt;Dockerfile&lt;/code&gt; and a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file to the project, which let you spin up the preview site using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once Kostis had done all the hard work, I added a small enhancement to make Astro's live preview work when you change files. That meant you could start the container and keep working while all your changes are instantly visible in the preview. That keeps the developer inner loop nice and tight.&lt;/p&gt;
&lt;p&gt;If you want to do the same, here's how to make it happen. Once again, I added a very small cherry to Kostis' wonderfully fluffy cake, so send your adoration his way.&lt;/p&gt;
&lt;h3 id=&quot;add-a-docker-compose-file&quot;&gt;Add a Docker compose file&lt;/h3&gt;
&lt;p&gt;Here's the &lt;code&gt;docker-compose.yml&lt;/code&gt; file for your Astro project. It goes in the root directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  astro:
    build: .
    ports:
 - &quot;3000:3000&quot;
    volumes:
 - .:/app
 - /app/node_modules
    environment:
 - NODE_ENV=development
 - HOST=0.0.0.0
    stdin_open: true
    tty: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This maps the volumes, with a special case for node_modules. It exposes Astro's port 3000 inside the container to port 3000 on your machine so you can open it in your browser.&lt;/p&gt;
&lt;h3 id=&quot;the-docker-file&quot;&gt;The Docker file&lt;/h3&gt;
&lt;p&gt;Here's the &lt;code&gt;Dockerfile&lt;/code&gt;, which also goes in your project's root directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;# Use Node 20 as the base image
FROM node:20-slim

# Install pnpm globally
ENV PNPM_HOME=&quot;/pnpm&quot;
ENV PATH=&quot;$PNPM_HOME:$PATH&quot;
RUN corepack enable

# Set the working directory
WORKDIR /app

# Copy package files
COPY package.json pnpm-lock.yaml* ./

# Install dependencies
RUN pnpm install

# Copy the rest of the source code
COPY . .

# Expose the default Astro port
EXPOSE 3000

# Start the dev server
CMD [&quot;pnpm&quot;, &quot;compose:dev&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's an optimization here around the package files, which is why they get their own copy command. There's an extra command in the &lt;code&gt;package.json&lt;/code&gt; file that we call here, too. It's a variation of the &lt;code&gt;dev&lt;/code&gt; script we use, but switches out the Astro run with a slight variation (the addition of the &lt;code&gt;--host&lt;/code&gt; flag) lets just show the important bits in this code snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;scripts&quot;: {     
    &quot;compose:dev&quot;: &quot;npm-run-all --parallel dev:img dev:dictionary compose:dev:astro dev:watch&quot;,
    &quot;compose:dev:astro&quot;: &quot;astro dev --host&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-vite-config-change&quot;&gt;The Vite config change&lt;/h3&gt;
&lt;p&gt;The final change is the one that makes the live refresh to work. This goes in your &lt;code&gt;astro.config.mjs&lt;/code&gt; file, and I popped it right after the existing &lt;code&gt;server&lt;/code&gt; config.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;server: {
    port: 3000
}
vite: {
    server: {
        watch: {
            usePolling: true,
        },
    },
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;spinning-it-up&quot;&gt;Spinning it up&lt;/h3&gt;
&lt;p&gt;The first time I ran this, I started things up with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up --build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then stop the container with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you haven't changed the container, you can start it with the faster:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19983/running-astro-in-a-preview-container</guid>
<pubDate>Mon, 08 Jun 2026 16:35:17 +0000</pubDate>
</item>
</channel>
</rss>