<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: LinkBook</title>
    <description>The latest articles on DEV Community by LinkBook (@hamzezarenasiri).</description>
    <link>https://dev.to/hamzezarenasiri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F386801%2Fb0d50e08-2c46-49f1-af4f-092440ecf4b9.png</url>
      <title>DEV Community: LinkBook</title>
      <link>https://dev.to/hamzezarenasiri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamzezarenasiri"/>
    <language>en</language>
    <item>
      <title>A bot enumerated every payment URL on our site and our logs never saw it</title>
      <dc:creator>LinkBook</dc:creator>
      <pubDate>Tue, 15 Sep 2026 08:22:36 +0000</pubDate>
      <link>https://dev.to/hamzezarenasiri/a-bot-enumerated-every-payment-url-on-our-site-and-our-logs-never-saw-it-28ai</link>
      <guid>https://dev.to/hamzezarenasiri/a-bot-enumerated-every-payment-url-on-our-site-and-our-logs-never-saw-it-28ai</guid>
      <description>&lt;p&gt;We migrated a customer panel's nginx container last week for an unrelated SEO fix (a trailing-slash redirect bug), rebuilt it, and redeployed. Routine stuff.&lt;/p&gt;

&lt;p&gt;A few days later I was pulling GA4 numbers to check on organic traffic and noticed something odd: roughly a quarter of our "Direct" channel sessions were landing on &lt;code&gt;/app/customer/login?returnTo=/payment/&amp;lt;plan&amp;gt;/&amp;lt;period&amp;gt;&lt;/code&gt; for a huge spread of plan slugs - not two or three, but nearly every pricing tier across every product we sell. Same browser fingerprint every time: &lt;code&gt;(not set)&lt;/code&gt;, desktop, Germany. Zero engagement on all of them.&lt;/p&gt;

&lt;p&gt;That's not a customer. That's a script walking through every checkout URL it could find, probably scraped off our own pricing page.&lt;/p&gt;

&lt;p&gt;I went to pull the nginx access log to find the source IP and got nothing. The container had been rebuilt days earlier for that unrelated SEO fix, and &lt;code&gt;access_log&lt;/code&gt; was pointed at a file inside the container (&lt;code&gt;/var/log/nginx/access.log&lt;/code&gt;), not stdout. The rebuild wiped it. &lt;code&gt;docker logs&lt;/code&gt; showed nothing because there was nothing on stdout to show. Our Promtail/Loki pipeline, which only scrapes the Docker logging driver, had never seen a single line of it either.&lt;/p&gt;

&lt;p&gt;The bot's IP is gone. There's no way to get it back now.&lt;/p&gt;

&lt;p&gt;The actual fix was two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/dev/stdout&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;error_log&lt;/span&gt; &lt;span class="n"&gt;/dev/stderr&lt;/span&gt; &lt;span class="s"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;nginx treats &lt;code&gt;/dev/stdout&lt;/code&gt; and &lt;code&gt;/dev/stderr&lt;/code&gt; as regular files it can write to, so this isn't some special integration, it just means the same log lines that used to sit in a file now go through the container's stdout/stderr, which &lt;code&gt;docker logs&lt;/code&gt; already reads and which our existing Promtail config was already scraping. Nothing else changed.&lt;/p&gt;

&lt;p&gt;The lesson wasn't really about the bot. Low-rate GET enumeration of public pricing URLs isn't a very interesting threat on its own. It was that we had a monitoring pipeline that looked complete (Promtail, Loki, Grafana, the works) with one container quietly opting out of it, because its base nginx config still defaulted to writing log files the way it would on a bare-metal box from ten years ago. If this container gets rebuilt again next month, whatever it's doing then, we'll actually be able to see it.&lt;/p&gt;

&lt;p&gt;If you're running nginx in Docker and haven't checked, it's worth a look at your own &lt;code&gt;access_log&lt;/code&gt;/&lt;code&gt;error_log&lt;/code&gt; directives. A monitoring stack is only as complete as the containers that actually write to it.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>docker</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>The Docker Compose Restart Policy Gotcha That Cost Us Two Weeks of Silent Downtime</title>
      <dc:creator>LinkBook</dc:creator>
      <pubDate>Sun, 30 Aug 2026 10:26:50 +0000</pubDate>
      <link>https://dev.to/hamzezarenasiri/the-docker-compose-restart-policy-gotcha-that-cost-us-two-weeks-of-silent-downtime-4ha7</link>
      <guid>https://dev.to/hamzezarenasiri/the-docker-compose-restart-policy-gotcha-that-cost-us-two-weeks-of-silent-downtime-4ha7</guid>
      <description>&lt;p&gt;We run a dozen+ services in plain &lt;code&gt;docker compose&lt;/code&gt; on a single box — no Swarm, no Kubernetes, just &lt;code&gt;docker-compose.prod.yml&lt;/code&gt; and &lt;code&gt;restart: always&lt;/code&gt; on everything. Or so I thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;A host reboot (unrelated maintenance) brought everything back up except three containers. Nobody noticed for two weeks, because the services that stayed down were dev-only instances and one internal monitoring stack — nothing customer-facing paged anyone.&lt;/p&gt;

&lt;p&gt;I found it by accident, running &lt;code&gt;docker ps -a&lt;/code&gt; for something unrelated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Names}}\t{{.Status}}'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"exited"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three containers, all &lt;code&gt;Exited (0)&lt;/code&gt; from &lt;strong&gt;two weeks ago&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker compose down&lt;/code&gt; (run once, on purpose, weeks earlier, for an unrelated fix) stops and removes containers. When they got recreated on the next &lt;code&gt;up -d&lt;/code&gt;, a few of them had drifted from the rest of the fleet: no explicit &lt;code&gt;restart&lt;/code&gt; policy in their compose block, which means Docker defaults to &lt;code&gt;restart: "no"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restart: "no"&lt;/code&gt; means exactly what it says — if the container exits for &lt;em&gt;any&lt;/em&gt; reason (a clean &lt;code&gt;down&lt;/code&gt;, a host reboot, an OOM kill), Docker will never bring it back. Not on daemon restart, not ever, until someone runs &lt;code&gt;up -d&lt;/code&gt; again by hand.&lt;/p&gt;

&lt;p&gt;The other ~90% of our services had &lt;code&gt;restart: always&lt;/code&gt; explicitly set, so a reboot was invisible for them. These three were added later by someone (me) who forgot the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-liner that would've caught it immediately
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;c &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-aq&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.HostConfig.RestartPolicy.Name}}'&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$policy&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"no"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Name}}'&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$policy&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's a step in our monthly infra audit, not something we find by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second half of this problem: zero-downtime redeploys
&lt;/h2&gt;

&lt;p&gt;This same fleet has another sharp edge worth knowing about if you're running plain compose in production: &lt;code&gt;docker compose up -d &amp;lt;service&amp;gt;&lt;/code&gt; for a redeploy briefly &lt;strong&gt;stops the old container before the new one is healthy&lt;/strong&gt; — a few seconds of 502s behind Traefik/nginx.&lt;/p&gt;

&lt;p&gt;We ended up writing a small blue-green script instead of reaching for Swarm/K8s just for this one problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;start a second container alongside the live one (&lt;code&gt;docker compose run -d --no-deps --name &amp;lt;container&amp;gt;_green &amp;lt;service&amp;gt;&lt;/code&gt; — has to be &lt;code&gt;run&lt;/code&gt;, not &lt;code&gt;up&lt;/code&gt;, since &lt;code&gt;up&lt;/code&gt; always reconciles to one container per service)&lt;/li&gt;
&lt;li&gt;poll its healthcheck until &lt;code&gt;healthy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;remove the old container, rename the new one into its place, restore &lt;code&gt;restart: always&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;on healthcheck timeout, remove the new container and leave the old one untouched — a safe no-op, not a rollback of something that already happened&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Script's public if useful: &lt;a href="https://github.com/Fanpino/deploy-bluegreen" rel="noopener noreferrer"&gt;https://github.com/Fanpino/deploy-bluegreen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neither of these is a novel idea. But "explicit restart policy on every service" and "compose &lt;code&gt;up -d&lt;/code&gt; isn't zero-downtime" are the kind of things that are obvious in hindsight and invisible until they cost you two weeks.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>selfhosted</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
