<?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: binadit</title>
    <description>The latest articles on DEV Community by binadit (@binadit).</description>
    <link>https://dev.to/binadit</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%2F3853937%2F7b742322-ef72-44c9-92e2-8a32b6f3aa67.png</url>
      <title>DEV Community: binadit</title>
      <link>https://dev.to/binadit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/binadit"/>
    <language>en</language>
    <item>
      <title>Staying on Microsoft 365 vs moving off it: the CLOUD Act law firms and accountants need to weigh</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Wed, 11 Nov 2026 08:33:27 +0000</pubDate>
      <link>https://dev.to/binadit/staying-on-microsoft-365-vs-moving-off-it-the-cloud-act-law-firms-and-accountants-need-to-weigh-504l</link>
      <guid>https://dev.to/binadit/staying-on-microsoft-365-vs-moving-off-it-the-cloud-act-law-firms-and-accountants-need-to-weigh-504l</guid>
      <description>&lt;h1&gt;
  
  
  The CLOUD Act doesn't care which EU region you picked
&lt;/h1&gt;

&lt;p&gt;Here's an uncomfortable fact for anyone running IT at a law firm or accountancy practice: choosing the EU data region in Microsoft 365 or Google Workspace does not change which country's courts can compel disclosure of your data. That's not a config setting you can fix. It's jurisdiction, and it follows the company, not the server rack.&lt;/p&gt;

&lt;p&gt;If you're the person who gets asked "where does our data actually sit, and under which law" by a partner or a client's privacy officer, this one's for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: location vs jurisdiction
&lt;/h2&gt;

&lt;p&gt;Microsoft Corporation and Google LLC are US entities. Doesn't matter if the disk is in Dublin or Frankfurt. Under the CLOUD Act (enacted 23 March 2018), a provider under US jurisdiction must disclose data in its "possession, custody, or control" when served valid US legal process, a warrant, subpoena, or court order, "regardless of whether such communication, record, or other information is located within or outside of the United States" (18 U.S.C. 2713).&lt;/p&gt;

&lt;p&gt;So the EU data region setting controls data residency. It does nothing for jurisdiction. Two separate axes, often confused as one.&lt;/p&gt;

&lt;p&gt;This matters more for legal and accounting workloads than most SaaS use cases because the data is frequently privileged: case strategy, undisclosed tax structuring, pre-signing M&amp;amp;A terms. A disclosure request doesn't even need to target your firm; it can be about a client or counterparty whose documents you're merely holding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underlying legal basis is also shaky
&lt;/h2&gt;

&lt;p&gt;Separate from the CLOUD Act, there's a GDPR transfer problem sitting underneath. Chapter V requires an adequacy decision or safeguards (like SCCs) for data leaving the EEA, and Article 48 explicitly states a foreign court order alone doesn't make a transfer lawful.&lt;/p&gt;

&lt;p&gt;The current transfer mechanism, the EU-US Data Privacy Framework (adopted 10 July 2023), is the third attempt after two predecessors got struck down by the CJEU:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safe Harbor, struck down 2015 (Schrems I)&lt;/li&gt;
&lt;li&gt;Privacy Shield, struck down July 2020 (Schrems II), specifically because FISA Section 702 left EU citizens with no effective remedy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the current framework is under challenge right now: dismissed by the EU General Court on 3 September 2025, appealed on 31 October 2025, pending before the CJEU as of writing.&lt;/p&gt;

&lt;p&gt;None of this makes today's setup illegal. But the pattern (replace, strike down, replace, challenge) is worth engineering around, not reacting to after a court decision lands on your desk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option A: stay put
&lt;/h2&gt;

&lt;p&gt;Valid choice for a lot of shops. Strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mature integrations with practice management, e-signature, billing tools&lt;/li&gt;
&lt;li&gt;Staff already trained, zero ramp-up&lt;/li&gt;
&lt;li&gt;EU data region + signed DPA available&lt;/li&gt;
&lt;li&gt;Security engineering budget a smaller vendor can't match&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reasonable if your client files rarely touch US persons, US litigation, or cross-border tax structuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option B: move to a provider outside US jurisdiction
&lt;/h2&gt;

&lt;p&gt;If the provider has no American parent and runs its own EU infrastructure, there's no US entity for a court order to reach. The CLOUD Act has nothing to attach to. The GDPR Chapter V question disappears too, since there's no third-country transfer to evaluate in the first place.&lt;/p&gt;

&lt;p&gt;Trade-offs to budget for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller product catalogue; check your add-in and integration dependencies before committing&lt;/li&gt;
&lt;li&gt;Migration project: mailboxes, calendars, shared drives, permissions, all need to move without losing version history or breaking client-facing addresses&lt;/li&gt;
&lt;li&gt;Fewer vendors in the market, so due diligence matters more. Get clear answers on who holds encryption keys and which law governs the provider, not marketing copy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick comparison
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Factor                 | MS365/Workspace (EU region) | European provider
------------------------|------------------------------|--------------------
CLOUD Act reach         | Yes, provider itself          | No US entity in chain
GDPR transfer question  | Relies on DPF, under appeal    | No transfer to assess
Migration effort         | None                          | One-time project
Support model            | Ticket-based, tiered           | Direct contact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decision framework
&lt;/h2&gt;

&lt;p&gt;Run through this before your next partner meeting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Low exposure&lt;/strong&gt;: rarely handle US persons, US litigation, or cross-border tax work, and comfortable with DPF pending appeal? Staying put with EU region + DPA is defensible. Revisit when the CJEU rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High exposure&lt;/strong&gt;: regularly hold privileged material for cross-border M&amp;amp;A, sanctions-adjacent sectors, HNWIs, or PEPs? The case for removing the jurisdiction question entirely gets stronger regardless of how the appeal goes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already asked&lt;/strong&gt;: has a client, insurer, or regulator asked in writing where your data sits and under which law applies? That's your signal the question stopped being theoretical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No universal right answer here. But "we picked the EU region" is not the same statement as "no foreign court can compel disclosure," and it's worth knowing which one you're actually telling your clients.&lt;/p&gt;

&lt;p&gt;Full legal detail and the complete comparison table: &lt;a href="https://binadit.com/blog/cloud-act-law-firms-stay-or-move-decision" rel="noopener noreferrer"&gt;original article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/cloud-act-law-firms-stay-or-move-decision" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>microsoft</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>How a SaaS platform cut infrastructure costs 46% by moving off Kubernetes</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 06 Nov 2026 08:20:00 +0000</pubDate>
      <link>https://dev.to/binadit/how-a-saas-platform-cut-infrastructure-costs-46-by-moving-off-kubernetes-3df4</link>
      <guid>https://dev.to/binadit/how-a-saas-platform-cut-infrastructure-costs-46-by-moving-off-kubernetes-3df4</guid>
      <description>&lt;h2&gt;
  
  
  When Kubernetes becomes the problem it was supposed to solve
&lt;/h2&gt;

&lt;p&gt;Here's a question worth asking before you adopt Kubernetes: do you actually have the problem it solves? A six-person SaaS team didn't, and it took them two years and EUR 14,200 a month to notice.&lt;/p&gt;

&lt;p&gt;This is a breakdown of how we helped them move off Kubernetes entirely and cut their infrastructure bill by 46%, with faster deploys and better uptime as a side effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The product: a B2B analytics platform built on Laravel, Vue, PostgreSQL (primary plus one replica), Redis, and a few background workers handling webhooks and scheduled reports.&lt;/p&gt;

&lt;p&gt;The infrastructure: three managed Kubernetes clusters (production, staging, and an "experiments" cluster nobody could justify), each with its own node pools, ingress controller, cert-manager instance, and full Prometheus/Grafana stack.&lt;/p&gt;

&lt;p&gt;The team had adopted Kubernetes two years earlier on a contractor's recommendation, during a growth phase. The contractor was long gone. Nobody had revisited whether the setup still made sense.&lt;/p&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploys took 8-12 minutes and often failed at the Helm chart stage with vague errors.&lt;/li&gt;
&lt;li&gt;Only 2 of 6 engineers could confidently debug the manifests under pressure.&lt;/li&gt;
&lt;li&gt;Autoscaling never behaved as expected, so the team hardcoded static replica counts, which quietly defeated the purpose of having autoscaling at all.&lt;/li&gt;
&lt;li&gt;Nobody knew the recovery plan if the Kubernetes control plane went down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No outages yet, but the team was burning roughly 15 hours a week just keeping the clusters alive. That's a real cost, even if it never shows up on an invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit found
&lt;/h2&gt;

&lt;p&gt;Two weeks of pure observation before touching anything: resource requests vs. actual usage, node utilization across a full business cycle, and a full pass through the cloud bill.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average CPU utilization across production nodes: &lt;strong&gt;11%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Average memory utilization: &lt;strong&gt;23%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Peak traffic over 90 days never came close to what a single well-sized server could handle&lt;/li&gt;
&lt;li&gt;42 pods running across three clusters for what mapped out to exactly &lt;strong&gt;6 logical services&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Node pools sized for an 8x traffic spike projected 18 months earlier that never materialized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also found: three separate managed load balancers, a duplicated logging pipeline shipping the same data to two destinations (leftover from an incomplete migration), and a staging cluster running 24/7 at near-production size for about six hours of actual weekly use.&lt;/p&gt;

&lt;p&gt;None of this was negligence. It's what happens when infrastructure gets provisioned for a future that doesn't arrive, and nobody goes back to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we moved off Kubernetes instead of fixing it
&lt;/h2&gt;

&lt;p&gt;Kubernetes earns its complexity when you need multi-team deployment isolation, workloads that scale from zero to hundreds of pods, or shared infra across many independently-owned services. This team had six services, six engineers, and two deploys a week. None of that applied.&lt;/p&gt;

&lt;p&gt;We proposed a simpler stack: a small fleet of dedicated servers behind a load balancer, running the app via Docker Compose and systemd, with PostgreSQL and Redis managed directly instead of as in-cluster StatefulSets.&lt;/p&gt;

&lt;p&gt;The reasoning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Operational match.&lt;/strong&gt; A team of six with two Kubernetes-literate engineers was running infra built for a team of thirty. Simplifying meant everyone could debug production, not just two people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost shape.&lt;/strong&gt; Control planes, redundant node pools, and managed add-ons carry fixed overhead no matter the load. At 11% utilization, that overhead buys nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning about failure.&lt;/strong&gt; Every engineer already understood "systemd restarts a crashed process." Nobody wanted to debug a CrashLoopBackOff caused by a readiness probe fighting an HPA.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be clear: this isn't a "Kubernetes bad" take. If this team ran 20+ independently deployed services or needed to scale a component to hundreds of replicas during traffic spikes, we'd have kept Kubernetes and just fixed the sizing. The decision follows the workload, not a philosophy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration, phase by phase
&lt;/h2&gt;

&lt;p&gt;Zero-downtime migration over six weeks, not a cutover weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: right-size based on observed peak, not theoretical peak&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2x app servers (8 vCPU, 16 GB RAM) behind HAProxy, running Docker Compose&lt;/li&gt;
&lt;li&gt;1x PostgreSQL primary (8 vCPU, 32 GB RAM) with a streaming replica&lt;/li&gt;
&lt;li&gt;1x Redis instance with persistence enabled&lt;/li&gt;
&lt;li&gt;2x worker nodes, scaled by systemd timers and a basic job-count check instead of an HPA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's roughly a third of the node count the Kubernetes setup needed, because there's no pod scheduling overhead, no duplicated system pods per node, and no headroom reserved for a scaling event that never happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: deployment pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Replaced the Helm-based CI/CD with build-and-rsync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker build -t app:$CI_COMMIT_SHA .&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker save app:$CI_COMMIT_SHA | gzip | ssh deploy@app-01 'docker load'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ssh deploy@app-01 'docker compose up -d --no-deps app'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts/healthcheck.sh app-01&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker save app:$CI_COMMIT_SHA | gzip | ssh deploy@app-02 'docker load'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ssh deploy@app-02 'docker compose up -d --no-deps app'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HAProxy pulls each node out of rotation during its deploy window, so the API stays up throughout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="n"&gt;app_servers&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="n"&gt;roundrobin&lt;/span&gt;
    &lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;httpchk&lt;/span&gt; &lt;span class="n"&gt;GET&lt;/span&gt; /&lt;span class="n"&gt;health&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;-&lt;span class="m"&gt;01&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;inter&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;fall&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;rise&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;-&lt;span class="m"&gt;02&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;11&lt;/span&gt;:&lt;span class="m"&gt;8080&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;inter&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;fall&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="n"&gt;rise&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Phase 3: database and cache&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL moved to dedicated hardware with tuned parameters (shared_buffers at 25% of RAM) and proper connection pooling via PgBouncer, which had been missing entirely in the cluster setup. Redis moved to a standalone instance with AOF persistence; previously it ran as an ephemeral pod that lost all queue state on every restart. That data loss bug had been open for four months before the audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: monitoring and runbooks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consolidated three separate Prometheus/Grafana stacks into one, and wrote runbooks for the five most common failure scenarios: database failover, worker backlog, disk pressure, certificate renewal, and application rollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results, six months later
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (K8s)&lt;/th&gt;
&lt;th&gt;After (dedicated servers)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly infra cost&lt;/td&gt;
&lt;td&gt;EUR 14,200&lt;/td&gt;
&lt;td&gt;EUR 7,650&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy time&lt;/td&gt;
&lt;td&gt;8-12 min&lt;/td&gt;
&lt;td&gt;90 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95 API response time&lt;/td&gt;
&lt;td&gt;410ms&lt;/td&gt;
&lt;td&gt;190ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineers who can debug production&lt;/td&gt;
&lt;td&gt;2 of 6&lt;/td&gt;
&lt;td&gt;6 of 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly ops time (team-wide)&lt;/td&gt;
&lt;td&gt;~15 hours&lt;/td&gt;
&lt;td&gt;~4 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptime (trailing 6 months)&lt;/td&gt;
&lt;td&gt;99.91%&lt;/td&gt;
&lt;td&gt;99.97%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Kubernetes isn't the villain here. The villain is infrastructure decisions that outlive the assumptions that justified them. If your cluster utilization is sitting in the teens and only two people on the team can debug a failed deploy, it might be worth asking whether you're solving a problem you actually have.&lt;/p&gt;

&lt;p&gt;Full writeup with more context: &lt;a href="https://binadit.com/blog/cloud-cost-optimization-services-kubernetes-migration-case-study" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/cloud-cost-optimization-services-kubernetes-migration-case-study" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to debug memory leaks in production without a zero downtime migration going sideways</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Wed, 04 Nov 2026 08:34:30 +0000</pubDate>
      <link>https://dev.to/binadit/how-to-debug-memory-leaks-in-production-without-a-zero-downtime-migration-going-sideways-1n9d</link>
      <guid>https://dev.to/binadit/how-to-debug-memory-leaks-in-production-without-a-zero-downtime-migration-going-sideways-1n9d</guid>
      <description>&lt;h1&gt;
  
  
  Your memory leak fix can cause the outage your leak never did
&lt;/h1&gt;

&lt;p&gt;Here's a scenario that plays out more often than anyone likes to admit: a service that ran fine at 400MB for months starts getting OOM-killed every day and a half, nobody can point to the deploy that caused it, and the on-call engineer's first instinct is to just restart things and ship a patch fast. That instinct is exactly how a slow leak turns into a self-inflicted outage. This is a process for finding the leak, proving the fix, and rolling it out without dropping a single request.&lt;/p&gt;

&lt;p&gt;Full reference: &lt;a href="https://binadit.com/blog/debugging-memory-leaks-production-zero-downtime-migration" rel="noopener noreferrer"&gt;debugging memory leaks in production without breaking your zero downtime migration&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before starting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shell access to the host or container, with permission to attach a profiler to a live process&lt;/li&gt;
&lt;li&gt;A load balancer or reverse proxy in front of the service (Nginx, HAProxy, cloud LB); without this, zero downtime isn't really on the table&lt;/li&gt;
&lt;li&gt;Memory, CPU, and latency metrics scraped at 30 second intervals or tighter. No metrics, no trend, no debugging. Set up node_exporter or equivalent first.&lt;/li&gt;
&lt;li&gt;The ability to spin up a second instance for canary testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples below use Node.js and systemd, but the process transfers directly to Ruby, Python, Java, or Go. Only the tooling changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Make sure it's actually a leak
&lt;/h2&gt;

&lt;p&gt;Caches warming up look like leaks for the first hour. A real leak climbs indefinitely and correlates with uptime, not traffic volume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 5 &lt;span class="s1"&gt;'ps -o pid,rss,etime,cmd -p 1234'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If RSS climbs linearly regardless of traffic and only drops on restart, that's a leak. If it climbs with traffic and then flattens, that's cache sizing, a different problem entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Pull evidence before you touch the process
&lt;/h2&gt;

&lt;p&gt;Restarting first is the number one mistake here. You lose the exact evidence you need to diagnose anything.&lt;/p&gt;

&lt;p&gt;Node.js, heap snapshot without stopping the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-USR2&lt;/span&gt; 1234   &lt;span class="c"&gt;# if a SIGUSR2 handler is wired up to write snapshots&lt;/span&gt;
&lt;span class="c"&gt;# or with --inspect already running:&lt;/span&gt;
ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 9229:127.0.0.1:9229 user@server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jmap &lt;span class="nt"&gt;-dump&lt;/span&gt;:live,format&lt;span class="o"&gt;=&lt;/span&gt;b,file&lt;span class="o"&gt;=&lt;/span&gt;heap.bin 1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python, no process interruption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;py-spy dump &lt;span class="nt"&gt;--pid&lt;/span&gt; 1234
py-spy record &lt;span class="nt"&gt;-o&lt;/span&gt; profile.svg &lt;span class="nt"&gt;--pid&lt;/span&gt; 1234 &lt;span class="nt"&gt;--duration&lt;/span&gt; 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take two snapshots, 30 to 60 minutes apart, under normal load. The diff is the actual signal. The absolute numbers aren't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Read the diff
&lt;/h2&gt;

&lt;p&gt;Load both snapshots into Chrome DevTools (Memory tab &amp;gt; Load) and use comparison view. Check for object counts that keep growing without a matching drop. Usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event listeners added without a matching &lt;code&gt;removeListener&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unbounded Maps or caches with no TTL, no max size, often keyed by user or request ID&lt;/li&gt;
&lt;li&gt;Closures holding onto large objects past the life of the request that created them&lt;/li&gt;
&lt;li&gt;Connection pools that grow without a ceiling under retry logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Java, run the dump through Eclipse MAT's Leak Suspects report. For Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;
&lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... later ...
&lt;/span&gt;&lt;span class="n"&gt;snapshot1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take_snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... wait ...
&lt;/span&gt;&lt;span class="n"&gt;snapshot2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take_snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;top_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lineno&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top_stats&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Reproduce it locally, don't just guess
&lt;/h2&gt;

&lt;p&gt;Load test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;autocannon &lt;span class="nt"&gt;-c&lt;/span&gt; 50 &lt;span class="nt"&gt;-d&lt;/span&gt; 300 http://localhost:3000/your-endpoint
&lt;span class="c"&gt;# in another terminal&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;ps &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;rss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; server.js&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10&lt;span class="p"&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;If local memory climbs the same way production did, you've confirmed it, and you've got a repeatable test to validate the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Patch it, then re-run the exact same test
&lt;/h2&gt;

&lt;p&gt;Add the TTL, remove the listener, cap the pool, whatever the fix is. Memory should plateau within a few minutes of steady load, not keep trending up.&lt;/p&gt;

&lt;p&gt;Working locally proves the code is right. It says nothing about whether the rollout is safe. Don't skip the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Ship it as a zero downtime migration, not a restart
&lt;/h2&gt;

&lt;p&gt;The instinct after finding a leak is to restart everything right now. Resist that. Treat it like any other production change.&lt;/p&gt;

&lt;p&gt;Behind a load balancer with multiple instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory deploy.yml &lt;span class="nt"&gt;--limit&lt;/span&gt; app-node-1

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/-/drain   &lt;span class="c"&gt;# app-specific drain endpoint&lt;/span&gt;
&lt;span class="c"&gt;# or for Nginx: mark backend down, reload, then stop the old process&lt;/span&gt;
nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Single instance behind systemd, use blue-green on the same box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the patched version on a new port&lt;/span&gt;
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3001 systemctl start app-patched.service

&lt;span class="c"&gt;# Health check before switching traffic&lt;/span&gt;
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:3001/health &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"not ready"&lt;/span&gt;

&lt;span class="c"&gt;# Flip the upstream&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/3000/3001/'&lt;/span&gt; /etc/nginx/conf.d/upstream.conf
nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload

&lt;span class="c"&gt;# Only now stop the old process&lt;/span&gt;
systemctl stop app.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order matters: start new, verify healthy, switch traffic, stop old. Do it out of order and your memory leak fix becomes the outage.&lt;/p&gt;

&lt;p&gt;On Kubernetes, same principle via rolling update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployment/app &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry/app:patched
kubectl rollout status deployment/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the readiness probe actually checks application health, not just that the port is listening. A pod that accepts connections before it's actually warmed up will drop requests mid-rollout, which defeats the point of doing this carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to know it actually worked
&lt;/h2&gt;

&lt;p&gt;Don't close the ticket right after deploy. Watch it for a full business cycle, ideally 24-48 hours.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RSS trend&lt;/strong&gt;: should plateau over 48 hours, not just slow its climb&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restart/OOM count&lt;/strong&gt;: &lt;code&gt;journalctl -u app.service | grep -i "oom"&lt;/code&gt; or &lt;code&gt;kubectl get events --field-selector reason=OOMKilling&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5xx rate during rollout&lt;/strong&gt;: should stay at baseline; a spike means your drain or readiness logic has a gap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GC pause times&lt;/strong&gt; (for GC'd runtimes): a healthier heap usually means shorter, more predictable p95 GC pauses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reasonable target: RSS variance over 48 hours should stay within 10-15% of its post-restart baseline.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/debugging-memory-leaks-production-zero-downtime-migration" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to ask your cloud act email provider the right questions, in writing</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:29:40 +0000</pubDate>
      <link>https://dev.to/binadit/how-to-ask-your-cloud-act-email-provider-the-right-questions-in-writing-2749</link>
      <guid>https://dev.to/binadit/how-to-ask-your-cloud-act-email-provider-the-right-questions-in-writing-2749</guid>
      <description>&lt;h2&gt;
  
  
  Your M365 contract won't save you from the CLOUD Act, but a paper trail might
&lt;/h2&gt;

&lt;p&gt;Here's an uncomfortable fact most engineering teams don't think about until a client or auditor forces the issue: your company's email, calendar, and files probably live under US jurisdiction regardless of which Azure or GCP region you picked in the admin console. Region settings control where bytes sit on disk. They don't control which country's courts can compel the provider to hand them over.&lt;/p&gt;

&lt;p&gt;Most teams never actually test this. They assume it, worry about it in a Slack thread, then move on. The fix is simpler than people expect: write down the right questions, send them to your provider in writing, and keep the answers on file.&lt;/p&gt;

&lt;p&gt;This isn't about catching Microsoft or Google in some conspiracy. Their engineering and security teams are solid. The real issue is jurisdiction, not product quality, and jurisdiction is a documentation problem you can actually solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before you send anything
&lt;/h3&gt;

&lt;p&gt;Three things to nail down first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find your actual contracting entity.&lt;/strong&gt; If you bought M365 through a reseller or MSP, your questions route through them first, not directly to Microsoft. Figure this out before drafting anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope the data type precisely.&lt;/strong&gt; Mailbox content, Teams transcripts, OneDrive files, and metadata (sender/recipient lists) can fall under different policies inside the same provider. Don't ask a vague question and expect a precise answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect legally careful answers, not evasive ones.&lt;/strong&gt; A US provider's legal team will answer using their own policy language. That's normal. Compare it against the actual law, not against what you hoped to hear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For background on why this matters legally, see the earlier piece on GDPR compliance for email providers. This post assumes you already know roughly what the CLOUD Act is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The questions to send (as a template)
&lt;/h3&gt;

&lt;p&gt;Send these as a numbered RFI to your account manager, with a request to forward to legal/compliance. Written answers only, no calls. A call gives you a vibe; an email gives you evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject: Written RFI - Data jurisdiction and disclosure questions

1. Which legal entity is our data processor, and in which country is it incorporated?
2. Does storing our data in an EU region change which country's law enforcement can compel disclosure?
3. Have you ever received a request for our data from a US authority, and could you tell us if you had?
4. Can you guarantee our data will never be disclosed to the US government without our consent or our national authorities' consent?
5. What is the current legal basis for transferring our personal data to the United States?
6. Is that legal basis currently being challenged, and what's the contingency if it's struck down?
7. Can a US court order override our European data processing agreement?
8. Who, specifically, can access our mailbox/file content, and under what process?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why these eight, specifically
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1 is the whole ballgame.&lt;/strong&gt; The CLOUD Act applies based on where the company is incorporated, not where servers sit. If the answer names Microsoft Corporation or Google LLC as the controlling entity, you already have your answer regardless of what follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2 kills a common misconception.&lt;/strong&gt; "EU data boundary" and "data residency in Europe" are storage-location features, not sovereignty guarantees. Same company, same keys, same exposure to a US court order. Get this distinction confirmed in writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3 exposes the gag-order problem.&lt;/strong&gt; Many government data requests come with a legal bar on notifying the customer. A provider that can't promise notification isn't stonewalling you, they're describing the law. Still worth having on paper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4 has already been answered publicly, which makes it a useful litmus test.&lt;/strong&gt; On 10 June 2025, Microsoft France's director of public and legal affairs, Anton Carniaux, testified before a French Senate inquiry. Asked if he could guarantee UGAP-hosted data would never be handed to US authorities without explicit French agreement, he said: "Non, je ne peux pas le garantir," adding it hadn't happened so far. No guarantee, no known incident. If your provider's answer looks similar, that's consistent with the law, not a provider-specific red flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5 pins down the transfer mechanism.&lt;/strong&gt; Under GDPR, personal data leaving the EEA needs an adequacy decision or safeguards like SCCs. For most US providers today, the answer is the EU-US Data Privacy Framework, adopted 10 July 2023.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6 is the real test.&lt;/strong&gt; The DPF is the third attempt at an EU-US transfer deal. Safe Harbor died in 2015. Privacy Shield died in 2020 over inadequate remedies for EU citizens under US surveillance law. The DPF itself is mid-challenge right now: dismissed by the EU General Court on 3 September 2025, appealed on 31 October 2025, pending before the CJEU with no ruling yet. Ask for the concrete fallback plan, not a restatement that the current framework is valid today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7 forces the conflict-of-laws admission.&lt;/strong&gt; GDPR Article 48 says a foreign court order doesn't automatically make a transfer lawful. Ask the provider how they reconcile that with US legal obligations when the two clash. Most will answer carefully here, because there's a real, unresolved conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8 is operational.&lt;/strong&gt; Does access require a warrant/subpoena/court order with internal legal review, or is it closer to automatic compliance? This tells you whether anyone would push back on your behalf.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do with the answers
&lt;/h3&gt;

&lt;p&gt;File all eight, in writing, with your data processing records. When a client or auditor asks "what happens if a US court comes knocking," you now have a documented position instead of a shrug.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://binadit.com/blog/cloud-act-email-provider-questions-in-writing" rel="noopener noreferrer"&gt;Read the full original article on binadit.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/cloud-act-email-provider-questions-in-writing" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Best practices for evaluating European alternatives to AWS, Azure and GCP</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:19:24 +0000</pubDate>
      <link>https://dev.to/binadit/best-practices-for-evaluating-european-alternatives-to-aws-azure-and-gcp-477d</link>
      <guid>https://dev.to/binadit/best-practices-for-evaluating-european-alternatives-to-aws-azure-and-gcp-477d</guid>
      <description>&lt;h2&gt;
  
  
  Someone asked you to evaluate EU cloud alternatives. Here's how not to waste three sprints on it
&lt;/h2&gt;

&lt;p&gt;Your DPO, a customer, or procurement just asked whether you could run on European infrastructure instead of AWS, Azure or GCP. This isn't about picking a side in the sovereignty debate; it's about giving a straight technical answer. What actually breaks if you move, what's portable, and what needs real validation before anyone signs off.&lt;/p&gt;

&lt;p&gt;This comes from running the same evaluation repeatedly for SaaS teams, agencies and e-commerce platforms going through procurement cycles or security questionnaires. None of it requires ditching Kubernetes, Terraform or your CI/CD setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: "EU region" and "EU jurisdiction" get treated as the same thing
&lt;/h3&gt;

&lt;p&gt;They're not. AWS, Azure and GCP all run EU data center regions, but the parent company is still bound by US law, CLOUD Act included, no matter where the bytes sit. If the actual requirement is jurisdictional separation, flipping a region dropdown on a hyperscaler doesn't solve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; write this distinction into your evaluation criteria before comparing a single vendor. Otherwise you'll spend weeks comparing the wrong thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: you're comparing brands instead of primitives
&lt;/h3&gt;

&lt;p&gt;Most SaaS stacks need the same handful of things: compute, object storage, managed Postgres or MySQL, a load balancer, a CDN. List what you actually use, by capability, not by AWS product name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example: workload inventory before evaluation
compute: 12 vCPU, autoscaling 2-8 nodes
object_storage: S3-compatible, 4TB, lifecycle rules
database: managed PostgreSQL 15, 200GB, daily PITR
cdn: edge caching, EU + UK pop coverage
queue: managed Redis, 2GB, persistence enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns a vague "can we replace AWS" into an answerable question: which EU providers offer S3-compatible storage, managed Postgres with PITR, and a comparable autoscaling setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: assuming object storage means a rewrite
&lt;/h3&gt;

&lt;p&gt;It usually doesn't. OVHcloud, Scaleway, Exoscale and Hetzner (via third-party gateways) all expose S3-compatible APIs. If your app talks to S3 through the standard SDK, repointing the endpoint and credentials is often enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.txt s3://my-bucket/test.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; https://s3.de.io.cloud.ovh.net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; eu-provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this smoke test before you plan anything bigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 4: the database layer is where things actually go wrong
&lt;/h3&gt;

&lt;p&gt;Compute and object storage are portable. Managed databases are not, this is where EU providers diverge most: feature parity, replication tooling, backup retention defaults. Before committing to anything, run a full backup/restore cycle on the target provider and measure recovery time for real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 5: latency assumptions based on a data center brochure
&lt;/h3&gt;

&lt;p&gt;Frankfurt or Amsterdam will serve EU traffic fine. If 20% of your users are in the UK, US or APAC, that's a different story. Run synthetic tests from multiple regions with &lt;code&gt;mtr&lt;/code&gt; or a distributed probe service, and look at p50/p95, not averages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 6: CDN "EU" claims that don't match actual routing
&lt;/h3&gt;

&lt;p&gt;Some EU-headquartered CDN resellers still route certain regions through non-EU origin infrastructure. If sovereignty is a real requirement, get a documented PoP list and trace actual request paths yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"@curl-format.txt"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; https://your-cdn-endpoint.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or a plain traceroute from your target markets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 7: certifications that apply to the reseller, not the facility
&lt;/h3&gt;

&lt;p&gt;Ask for the ISO 27001 (and NEN 7510 where relevant) of the physical data center, not the managed service layered on top. A logo on a website isn't a certificate. Request the actual scope statement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 8: pricing only the compute line
&lt;/h3&gt;

&lt;p&gt;European providers often look cheaper per vCPU hour, until you add egress, backup storage, IP allocation and support tiers. Build a like-for-like monthly cost model across at least three providers using your real usage pattern, not a generic instance size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 9: IaC that was never tested against the new provider
&lt;/h3&gt;

&lt;p&gt;If your Terraform targets AWS-specific resources, you usually need a provider swap, not a rewrite, for standard compute and networking. Run &lt;code&gt;terraform plan&lt;/code&gt; against a sandbox account early.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"openstack"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;auth_url&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ovh_auth_url&lt;/span&gt;
  &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="nx"&gt;tenant_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ovh_project_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"openstack_compute_instance_v2"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-node-1"&lt;/span&gt;
  &lt;span class="nx"&gt;flavor_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"b2-15"&lt;/span&gt;
  &lt;span class="nx"&gt;image_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Ubuntu 22.04"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches unsupported resource types (certain autoscaling policies, specific VPC peering models) before they block you mid-migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 10: treating the cutover as a single switch
&lt;/h3&gt;

&lt;p&gt;Run both environments in parallel with DNS-weighted traffic splitting. Validate error rates and latency at each traffic percentage. Keep the old environment warm through at least one full peak traffic cycle. This is standard practice for any zero-downtime migration, and it's what keeps a sovereignty decision from turning into an outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to actually roll this out
&lt;/h3&gt;

&lt;p&gt;Don't try to do all ten in one sprint.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1 (1-2 weeks):&lt;/strong&gt; workload inventory and requirement separation. Pure documentation, gives procurement or legal something concrete to work with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 (2-4 weeks):&lt;/strong&gt; sandbox validation against at least two candidate providers. Runs in parallel with production, costs a few hundred euros in test resources, produces real numbers instead of vendor claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 3 (4-10 weeks):&lt;/strong&gt; staged migration with IaC parity testing and a reversible cutover. Keep a rollback window of at least one full business cycle before decommissioning anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assign one engineer as technical owner from day one. Splitting this across the team produces benchmarks that don't line up later.&lt;/p&gt;

&lt;p&gt;None of this requires faith. It requires inventory, benchmarking and a staged rollout, the same discipline you'd apply to any infrastructure change.&lt;/p&gt;

&lt;p&gt;Full version with more context here: &lt;a href="https://binadit.com/blog/best-practices-private-cloud-infrastructure-eu-alternatives-aws-azure-gcp" rel="noopener noreferrer"&gt;Best practices for evaluating European alternatives to AWS, Azure and GCP&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/best-practices-private-cloud-infrastructure-eu-alternatives-aws-azure-gcp" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
    <item>
      <title>Why cheap hosting becomes expensive, and how managed cloud infrastructure fixes the math</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 16 Oct 2026 07:03:43 +0000</pubDate>
      <link>https://dev.to/binadit/why-cheap-hosting-becomes-expensive-and-how-managed-cloud-infrastructure-fixes-the-math-4p2k</link>
      <guid>https://dev.to/binadit/why-cheap-hosting-becomes-expensive-and-how-managed-cloud-infrastructure-fixes-the-math-4p2k</guid>
      <description>&lt;h2&gt;
  
  
  Your €30/month VPS is lying to you about its real cost
&lt;/h2&gt;

&lt;p&gt;Every engineer who has run a Friday-afternoon incident on a budget VPS knows this story. The invoice says €30. The actual bill includes a 20-minute checkout outage, a Slack thread full of panic, and three hours of your time spent figuring out why MySQL is refusing connections. None of that shows up on the hosting receipt. It shows up in your sprint velocity.&lt;/p&gt;

&lt;p&gt;This is the real pattern behind most migrations to managed infrastructure: the sticker price was never the full price. Let's break down why, and what actually fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why cheap hosting is a cost-shifting machine, not a cost-saving one
&lt;/h2&gt;

&lt;p&gt;Budget hosting plans work on oversubscription: sell the same CPU and memory pool to more tenants than could ever use it simultaneously. Fine, until your app actually needs what it was promised.&lt;/p&gt;

&lt;p&gt;Three failure modes show up constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Noisy neighbors.&lt;/strong&gt; Shared I/O and CPU scheduling means someone else's batch job can wreck your p95 latency without you changing a single line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No headroom for peaks.&lt;/strong&gt; Plans are sized for average load. A 3x traffic spike from a launch or a viral post pushes memory past the limit, and the OOM killer starts picking off PHP-FPM workers or your database process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero engineering time baked in.&lt;/strong&gt; Nobody tunes &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt;, rotates logs before disk fills, or watches your TLS cert expiry. That work doesn't vanish, it just gets dumped on whoever's on call, mid-incident instead of before one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The math, concretely
&lt;/h3&gt;

&lt;p&gt;A €30/month plan versus a properly sized €200/month managed VPS looks like €2,040/year in savings. Now add two checkout outages a year at 25 minutes each, on a store doing €500/day: roughly €520 in lost sales. Add 4-6 engineering hours to diagnose and recover at a €75/hour loaded cost: another €300-450. The "cheap" plan is already more expensive before you count what that engineer could've shipped instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the cost actually hides
&lt;/h2&gt;

&lt;p&gt;In migrations off budget hosting, the hidden cost clusters in three spots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database performance degrading silently until one slow query takes the whole site down.&lt;/li&gt;
&lt;li&gt;No staging environment that resembles production, so customers find bugs before CI does.&lt;/li&gt;
&lt;li&gt;Manual TLS renewal that gets missed once a year and kills the whole domain for hours.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are exotic problems. They're recurring operational work that a €30/month plan was never designed to cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: size for reality, automate the boring stuff, isolate the database
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Size compute to peak, not average
&lt;/h3&gt;

&lt;p&gt;Pull 90 days of CPU/memory metrics. No data? That's a signal on its own. Quick baseline on Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vmstat 2 10
free &lt;span class="nt"&gt;-m&lt;/span&gt;
mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Target 60-70% utilization at peak, not 95%. If campaigns spike you 3x, provision for that or put autoscaling in front of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Separate the database from the app tier
&lt;/h3&gt;

&lt;p&gt;On budget hosting, MySQL and your web server fight for the same resources. Splitting them onto two modest instances kills most noisy-neighbor issues. Baseline tuning for a dedicated MySQL 8 box with 8GB RAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;innodb_buffer_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5G&lt;/span&gt;
&lt;span class="py"&gt;innodb_log_file_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
&lt;span class="py"&gt;max_connections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;150&lt;/span&gt;
&lt;span class="py"&gt;innodb_flush_log_at_trx_commit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;query_cache_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buffer pool at roughly 60-70% of RAM, leaving room for connections and OS cache. On a 2GB plan this is simply not possible, which is the actual reason your 40ms query takes 900ms on shared hosting.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automate what fails silently
&lt;/h3&gt;

&lt;p&gt;Cert expiry and disk-filling logs cause most "surprise" outages. Both fixable in under an hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# certbot auto-renewal via systemd timer, twice daily&lt;/span&gt;
certbot renew &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--deploy-hook&lt;/span&gt; &lt;span class="s2"&gt;"systemctl reload nginx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Build staging that actually resembles production
&lt;/h3&gt;

&lt;p&gt;If staging is a subdirectory with debug mode flipped on, it's not catching production-class bugs. Match PHP version, DB engine version, and resource ratios (smaller is fine, different is not). Skip this and every deploy becomes a gamble.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to know it actually worked
&lt;/h2&gt;

&lt;p&gt;Don't trust "it feels faster." Check numbers before and after:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Budget hosting&lt;/th&gt;
&lt;th&gt;Right-sized managed infra&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P95 response time&lt;/td&gt;
&lt;td&gt;800-1500ms&lt;/td&gt;
&lt;td&gt;150-300ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB query p95&lt;/td&gt;
&lt;td&gt;300-900ms&lt;/td&gt;
&lt;td&gt;20-60ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unplanned restarts/month&lt;/td&gt;
&lt;td&gt;2-5&lt;/td&gt;
&lt;td&gt;0-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory headroom at peak&lt;/td&gt;
&lt;td&gt;&amp;lt;10%&lt;/td&gt;
&lt;td&gt;30-40%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Validation checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mysqladmin status&lt;/code&gt; to confirm no connection queueing under load&lt;/li&gt;
&lt;li&gt;Load test at 2-3x normal peak with k6, watch 5xx rates and latency trend, not just averages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;free -m&lt;/code&gt; during peak, zero swap usage, any swap activity means you're still undersized&lt;/li&gt;
&lt;li&gt;Measure TTFB separately from full page load, they diagnose different bottlenecks&lt;/li&gt;
&lt;li&gt;If you have incident history, track MTTR before/after; properly managed infra tends to shift this from hours to minutes because failover and monitoring are built in&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping it fixed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Review capacity quarterly, not after an incident&lt;/li&gt;
&lt;li&gt;Treat SSL renewal, backups, and patching as code, not a human's memory&lt;/li&gt;
&lt;li&gt;Load test before big traffic events, not after an outage during one&lt;/li&gt;
&lt;li&gt;Isolate the database tier early, it's the first and most common contention point&lt;/li&gt;
&lt;li&gt;Keep staging close enough to production to catch real bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this needs exotic tooling. It needs someone accountable for checking it on a schedule. That's usually the actual gap.&lt;/p&gt;

&lt;p&gt;Full writeup with more detail: &lt;a href="https://binadit.com/blog/why-cheap-hosting-becomes-expensive-managed-cloud-infrastructure" rel="noopener noreferrer"&gt;Why cheap hosting becomes expensive, and how managed cloud infrastructure fixes the math&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/why-cheap-hosting-becomes-expensive-managed-cloud-infrastructure" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>A 12-question sovereignty audit every DPO should run</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:15:10 +0000</pubDate>
      <link>https://dev.to/binadit/a-12-question-sovereignty-audit-every-dpo-should-run-3669</link>
      <guid>https://dev.to/binadit/a-12-question-sovereignty-audit-every-dpo-should-run-3669</guid>
      <description>&lt;h1&gt;
  
  
  Your vendor says 'no US subprocessors.' Here's how to actually check
&lt;/h1&gt;

&lt;p&gt;Your DPO just forwarded you another vendor questionnaire with a checked box next to "no US subprocessors." Before you forward it back with a thumbs up, know this: most of those boxes get checked optimistically, not accurately. If you're the engineer who ends up answering "where does our data actually live" during procurement calls, this list is for you.&lt;/p&gt;

&lt;p&gt;The gap is rarely malicious. It's usually a monitoring tool that defaulted to a US region, a backup target nobody revisited after setup, or a subprocessor that's legally based in the EU but owned by a US parent. Sovereignty audits exist to find exactly that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why yes/no questionnaires fail
&lt;/h2&gt;

&lt;p&gt;A vendor questionnaire asks "do you store data in the EU?" and gets a yes. It doesn't ask who holds the encryption keys, where support engineers sit, or whether the CDN in front of the app routes through a US edge network. Evidence beats assertions every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks that actually matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Subprocessor list freshness.&lt;/strong&gt; If a vendor's public subprocessor list hasn't changed in over a year despite shipping new features, it's probably stale, not accurate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parent company ownership, not just contracting entity.&lt;/strong&gt; A subprocessor incorporated in Germany can still be a wholly owned US subsidiary, which pulls it under the CLOUD Act regardless of server location. Always ask for the ultimate parent, not the signing entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support and escalation location.&lt;/strong&gt; If a US-based support engineer can remote into production to debug something, that's a data access path your residency clause doesn't cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring and APM defaults.&lt;/strong&gt; Sentry, Datadog, and New Relic all offer EU hosting, but it's opt-in. Primary infra can be EU-only while telemetry leaks through a third-party integration nobody checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDN and DNS routing.&lt;/strong&gt; A CDN is a subprocessor too. Origin in Frankfurt doesn't mean much if the CDN is a US hyperscaler's edge network. You can verify this yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check which edge network is actually serving a domain&lt;/span&gt;
dig +short CNAME cdn.example.com

&lt;span class="c"&gt;# Trace the resolved IP's registered location&lt;/span&gt;
whois &lt;span class="si"&gt;$(&lt;/span&gt;dig +short cdn.example.com | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This won't give you a legal answer, but it'll tell you fast whether the CDN claim matches DNS reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup and DR region.&lt;/strong&gt; EU-only production paired with a US backup target is common, usually because backup tooling defaulted there during setup and nobody revisited it. Ask explicitly where backups, snapshots, and DR replicas live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who holds the encryption keys, and from where.&lt;/strong&gt; Encryption at rest is table stakes. If a US entity or cloud provider holds the keys, EU residency doesn't block a lawful access request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legal basis for remaining transfers.&lt;/strong&gt; If zero international transfers isn't achievable, find out if they're relying on SCCs, BCRs, or something else, and whether it's been tested against post-Schrems II expectations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documented incident response for law enforcement requests.&lt;/strong&gt; Ask what happens procedurally if a non-EU authority requests data. If there's no written process, there's no real process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether the infra partner's own tooling is sovereign.&lt;/strong&gt; Some vendors sell you an EU-region sovereignty story while their own ticketing or remote access tooling runs on US SaaS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failover and migration behavior.&lt;/strong&gt; Sovereignty guarantees are easiest to break during a DR drill or fast migration. Ask the vendor to walk through a specific failover scenario and confirm the DR target still meets the same residency commitments as production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct technical contact.&lt;/strong&gt; Can someone answer these questions in a conversation, or does everything route through a ticket queue with a copy-pasted compliance statement three days later? The former usually means they understand their own subprocessor chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the real subprocessor table, not a summary
&lt;/h2&gt;

&lt;p&gt;Ask for the DPA's actual subprocessor annex. It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subprocessor      | Purpose              | Entity location    | Parent HQ
EU hosting partner| Infrastructure       | Netherlands        | Netherlands
EU email relay    | Transactional email  | Germany             | Germany
Monitoring vendor | APM / error tracking | EU region (opt-in)  | United States
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third row fails a strict no-US-subprocessor policy even though the data is technically hosted in the EU. The parent HQ column is the one most vendor tables conveniently leave out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling this out without reopening every contract at once
&lt;/h2&gt;

&lt;p&gt;Most teams have ten to thirty vendors already under contract. Don't try to audit all of them simultaneously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tier vendors by how much regulated or personal data they touch&lt;/li&gt;
&lt;li&gt;Run the full check against top-tier vendors first: core infra, email/identity, anything handling payment or health data&lt;/li&gt;
&lt;li&gt;Bake the subprocessor table requirement into procurement for new vendors from day one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full details and context: &lt;a href="https://binadit.com/blog/no-us-subprocessor-sovereignty-audit-dpo-checklist" rel="noopener noreferrer"&gt;the original sovereignty audit checklist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/no-us-subprocessor-sovereignty-audit-dpo-checklist" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>How to automate SSL certificate renewal across a fleet of servers</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 09 Oct 2026 07:44:08 +0000</pubDate>
      <link>https://dev.to/binadit/how-to-automate-ssl-certificate-renewal-across-a-fleet-of-servers-1k0c</link>
      <guid>https://dev.to/binadit/how-to-automate-ssl-certificate-renewal-across-a-fleet-of-servers-1k0c</guid>
      <description>&lt;h1&gt;
  
  
  Stop babysitting SSL certs: automate renewal across your whole fleet
&lt;/h1&gt;

&lt;p&gt;If someone on your team still has a recurring calendar reminder to check certificate expiry dates, you have a problem worth fixing today. Not next quarter, today. Manual cert tracking across multiple hosts is the kind of low-grade recurring work that quietly eats hours every month and never shows up as a line item until something expires in production at 2am.&lt;/p&gt;

&lt;p&gt;This guide walks through building fleet-wide automated renewal using Let's Encrypt, the ACME protocol, and a central orchestration pattern. It works whether you have 5 boxes or 500, behind a load balancer, as separate app nodes, or spread across regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assumptions before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You control DNS (or can hit a DNS API) for Cloudflare, Route 53, DigitalOcean, etc.&lt;/li&gt;
&lt;li&gt;Linux servers (Ubuntu 22.04/24.04 in examples, distro-agnostic approach)&lt;/li&gt;
&lt;li&gt;SSH access across the fleet, ideally via Ansible or Salt, not manual loops&lt;/li&gt;
&lt;li&gt;TLS termination happens at Nginx or HAProxy, not inside the app itself&lt;/li&gt;
&lt;li&gt;You're fine with cron or systemd timers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll use certbot since it has the broadest plugin support, though acme.sh or lego work fine with the same architecture if you want something lighter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a validation method that actually scales
&lt;/h2&gt;

&lt;p&gt;HTTP-01 validation needs port 80 reachable on the exact host being validated. That's fine for one server. It falls apart across a fleet behind a load balancer, where traffic for a domain doesn't predictably land on the same box twice.&lt;/p&gt;

&lt;p&gt;DNS-01 is the better default. No inbound traffic required, supports wildcards, and lets you centralize issuance on one host even if the cert gets distributed everywhere else.&lt;/p&gt;

&lt;p&gt;Cloudflare example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot python3-certbot-dns-cloudflare &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/letsencrypt/cloudflare.ini &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
dns_cloudflare_api_token = YOUR_TOKEN
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /etc/letsencrypt/cloudflare.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Centralize issuance, distribute the result
&lt;/h2&gt;

&lt;p&gt;Don't run certbot independently on every server. That gives you inconsistent cert state across the fleet and more surfaces that can silently fail. Issue once, push everywhere.&lt;/p&gt;

&lt;p&gt;Use a deploy hook to push the renewed cert out via your config management tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /etc/letsencrypt/live/example.com/fullchain.pem /etc/haproxy/certs/example.com.pem
systemctl reload haproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep that logic in one deploy hook script, not scattered across playbooks. One script, one source of truth for what happens post-renewal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't forget the reload
&lt;/h2&gt;

&lt;p&gt;Copying a new cert file does nothing if the service is still holding the old one in memory. Every distribution step needs a paired reload handler, Nginx or HAProxy, whichever terminates TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert on failure, not just on success
&lt;/h2&gt;

&lt;p&gt;A cron job that fails silently for a month isn't automation, it's a delayed incident waiting to happen. Wrap the renewal and check the exit code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; certbot renew &lt;span class="nt"&gt;--quiet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text":"Certbot renewal failed on '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the webhook for whatever your team already uses, PagerDuty, Opsgenie, Slack. The point is a human sees the failure within a day, not when the cert is about to expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify it actually works
&lt;/h2&gt;

&lt;p&gt;Don't trust the cron job exists and assume it's fine. Check end to end.&lt;/p&gt;

&lt;p&gt;Dry run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check expiry across the fleet:&lt;br&gt;
&lt;/p&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;host &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;fleet_hosts.txt&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="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;: "&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
    | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-enddate&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better yet, wire cert expiry into your existing monitoring. Prometheus blackbox exporter, Uptime Kuma, Zabbix all support it natively. Alert when &lt;code&gt;probe_ssl_earliest_cert_expiry&lt;/code&gt; drops below 14 days, this is your independent confirmation layer outside certbot's own logs.&lt;/p&gt;

&lt;p&gt;And confirm distribution reached every node, not just the one that ran certbot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible web_fleet &lt;span class="nt"&gt;-m&lt;/span&gt; shell &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"openssl x509 -in /etc/ssl/certs/example.com.pem -noout -enddate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mismatched expiry date across hosts means distribution failed somewhere, which is a more common failure mode than renewal itself breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls worth avoiding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hardcoding API tokens in playbooks instead of using a secrets manager&lt;/li&gt;
&lt;li&gt;Forgetting the service reload after distribution&lt;/li&gt;
&lt;li&gt;Hitting Let's Encrypt's rate limit (50 certs per registered domain per week), easy to hit if you're doing per-subdomain certs instead of a wildcard&lt;/li&gt;
&lt;li&gt;Running certbot independently per server instead of centralizing&lt;/li&gt;
&lt;li&gt;Shipping automation with zero failure alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;The same pattern, central issuance plus fleet-wide distribution via config management, extends naturally to SSH key rotation, firewall rule pushes, and post-migration config redeploys. If you're running this at scale, folding it into a GitOps pipeline means one less automation system to monitor separately from your app deployments.&lt;/p&gt;

&lt;p&gt;Full original writeup with more detail: &lt;a href="https://binadit.com/blog/automate-ssl-certificate-renewal-cloud-cost-optimization-services" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/automate-ssl-certificate-renewal-cloud-cost-optimization-services" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sslautomation</category>
      <category>letsencrypt</category>
      <category>devops</category>
      <category>serverfleetmanagement</category>
    </item>
    <item>
      <title>Understanding what an Outlook alternative actually needs to do to keep mail in Europe</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Wed, 07 Oct 2026 09:43:02 +0000</pubDate>
      <link>https://dev.to/binadit/understanding-what-an-outlook-alternative-actually-needs-to-do-to-keep-mail-in-europe-460c</link>
      <guid>https://dev.to/binadit/understanding-what-an-outlook-alternative-actually-needs-to-do-to-keep-mail-in-europe-460c</guid>
      <description>&lt;h2&gt;
  
  
  Switching your inbox region doesn't change who owns the data
&lt;/h2&gt;

&lt;p&gt;If you've ever ticked "EU data region" in Microsoft 365 or Google Workspace and considered the jurisdiction problem solved, here's the uncomfortable part: it isn't. The US CLOUD Act (2018) says a provider under US jurisdiction must hand over data under its possession, custody or control when a US court orders it, regardless of which country the servers physically sit in. The law tracks the company, not the data centre. Let's get into why that matters for anyone planning a real outlook alternative, and what actually has to change under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  The jurisdiction problem, in plain terms
&lt;/h3&gt;

&lt;p&gt;Outlook is the client. Microsoft 365 is the service. For most orgs those have merged into one experience, but legally they're two different things, and the service is what matters here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Corporation: American company, subject to US law&lt;/li&gt;
&lt;li&gt;Google LLC: American company, subject to US law&lt;/li&gt;
&lt;li&gt;CLOUD Act: applies based on &lt;em&gt;custody&lt;/em&gt;, not server location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meanwhile GDPR Chapter V (Articles 44-50) restricts personal data leaving the EEA without an adequacy decision or safeguards like SCCs. Article 48 is the key detail most teams miss: a foreign court order doesn't automatically make a transfer lawful. Article 24 puts the compliance burden on you, the controller, not the vendor.&lt;/p&gt;

&lt;p&gt;These two legal regimes don't reconcile cleanly, and the courts have said so three times:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Safe Harbor, struck down by the CJEU in 2015&lt;/li&gt;
&lt;li&gt;Privacy Shield, struck down in Schrems II (July 2020), over FISA Section 702 giving EU citizens no real remedy against US government access&lt;/li&gt;
&lt;li&gt;EU-US Data Privacy Framework, adopted 10 July 2023, currently in force, but under active challenge: dismissed by the EU General Court on 3 September 2025, appealed 31 October 2025, and now pending before the CJEU&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want the sworn-testimony version of this: on 10 June 2025, Microsoft France's director of public and legal affairs, Anton Carniaux, was asked under oath before a French Senate inquiry whether he could guarantee French citizens' data would never be handed to the US government without French authorisation. His answer: "Non, je ne peux pas le garantir" ("No, I cannot guarantee that"), while noting it hadn't happened so far. No guarantee, no known incident. Both halves matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the exposure actually lives in the mail pipeline
&lt;/h3&gt;

&lt;p&gt;When mail moves through Microsoft 365, it typically passes through multiple Microsoft-controlled systems, each one a separate copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send -&amp;gt; Exchange Online mailbox
     -&amp;gt; Spam/malware filtering
     -&amp;gt; Journaling/compliance archive
     -&amp;gt; Legal hold / eDiscovery store (if applicable)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those is under Microsoft's custody regardless of region settings. Picking an EU data centre changes the postal address of the hardware. It does not change who has custody.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a real migration actually requires
&lt;/h3&gt;

&lt;p&gt;Say you're moving a 40-person org off Microsoft 365: Outlook, OneDrive, Teams, plus a shared &lt;code&gt;info@&lt;/code&gt; mailbox watched by three people. Here's the checklist, piece by piece:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protocols the new provider must support:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMAP / SMTP   -&amp;gt; mail
CalDAV        -&amp;gt; calendar
CardDAV       -&amp;gt; contacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the replacement is web-app-only with no protocol support, you've just swapped one lock-in for another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared mailbox permissions:&lt;/strong&gt; &lt;code&gt;info@&lt;/code&gt; needs multi-user access control without issuing separate logins per person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS/auth records to rebuild:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPF record  - sender authorization
DKIM record - message signing
DMARC record - policy enforcement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get these wrong and your outgoing mail gets flagged as spam.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client UX:&lt;/strong&gt; People are used to the three-pane layout (folders / message list / reading pane). Force everyone into a browser tab and adoption drops, independent of how solid the backend is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File sync:&lt;/strong&gt; Needs to replicate "files on demand" behavior, files show as available without consuming local disk until opened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meetings:&lt;/strong&gt; Guests need to join via browser link, no install required, since you can't mandate your internal tooling on every client or supplier.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Binadit Workspace addresses this
&lt;/h3&gt;

&lt;p&gt;Binadit Workspace covers that full checklist as one product, one login, on the customer's own domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mail/calendar/contacts over IMAP, SMTP, CalDAV, CardDAV, and JMAP, so existing clients keep working&lt;/li&gt;
&lt;li&gt;Shared mailboxes with per-colleague permissions&lt;/li&gt;
&lt;li&gt;DNS records (MX, SPF, DKIM, DMARC, autoconfig) placed automatically via Binadit's own DNS tool&lt;/li&gt;
&lt;li&gt;Northmail: free macOS/Windows mail client in the familiar three-pane layout, connects to Workspace, on-prem Exchange, and Gmail simultaneously, useful during phased migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bottom line
&lt;/h3&gt;

&lt;p&gt;An outlook alternative isn't about finding a nicer inbox UI. It's about finding a provider under a different legal jurisdiction, one that actually replaces the full protocol stack your org depends on, not just the icon on the desktop.&lt;/p&gt;

&lt;p&gt;Full writeup with the legal citations: &lt;a href="https://binadit.com/blog/outlook-alternative-keeps-mail-in-europe" rel="noopener noreferrer"&gt;https://binadit.com/blog/outlook-alternative-keeps-mail-in-europe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/outlook-alternative-keeps-mail-in-europe" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>outlookalternative</category>
      <category>emailsovereignty</category>
      <category>gdpremailhosting</category>
      <category>cloudact</category>
    </item>
    <item>
      <title>How to set up website server performance that holds under real traffic</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 25 Sep 2026 07:53:52 +0000</pubDate>
      <link>https://dev.to/binadit/how-to-set-up-website-server-performance-that-holds-under-real-traffic-31c5</link>
      <guid>https://dev.to/binadit/how-to-set-up-website-server-performance-that-holds-under-real-traffic-31c5</guid>
      <description>&lt;h2&gt;
  
  
  Your server works fine until it doesn't
&lt;/h2&gt;

&lt;p&gt;Every infrastructure engineer knows this moment: the server hums along fine in staging, then a traffic spike hits production and everything falls over. Requests queue, workers pin at max, response times spike from 200ms to 8 seconds. The usual reaction is to throw more CPU at it. That's rarely the actual problem.&lt;/p&gt;

&lt;p&gt;Most servers that collapse under load aren't under-resourced, they're misconfigured. The default settings for PHP-FPM, Nginx, and your database connections were never meant for production traffic. Here's the exact sequence of changes that fixes this, in the order that actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: measure before you touch anything
&lt;/h2&gt;

&lt;p&gt;You can't prove a fix worked without a baseline. Run this first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ab &lt;span class="nt"&gt;-n&lt;/span&gt; 1000 &lt;span class="nt"&gt;-c&lt;/span&gt; 50 https://yourdomain.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record requests per second, mean response time, and failed request count. Check &lt;code&gt;uptime&lt;/code&gt; and &lt;code&gt;vmstat 1 5&lt;/code&gt; during the test too. Write it all down; you'll compare against it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: PHP-FPM is probably misconfigured
&lt;/h2&gt;

&lt;p&gt;The default pool config is not production-ready. Open it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/php/8.3/fpm/pool.d/www.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Size your workers based on actual memory usage, not guesswork. Check real footprint per worker with &lt;code&gt;ps aux | grep php-fpm&lt;/code&gt; (typically 30-60MB for WordPress/Laravel), then divide available RAM by that number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;pm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic&lt;/span&gt;
&lt;span class="py"&gt;pm.max_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;40&lt;/span&gt;
&lt;span class="py"&gt;pm.start_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;pm.min_spare_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;pm.max_spare_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;15&lt;/span&gt;
&lt;span class="py"&gt;pm.max_requests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pm.max_requests&lt;/code&gt; is underrated. It recycles workers periodically, which stops slow memory leaks from degrading your server over hours of uptime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Nginx is dropping connections it doesn't need to
&lt;/h2&gt;

&lt;p&gt;Reduce TCP handshake overhead with proper keepalive and connection settings:&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;worker_processes&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;worker_rlimit_nofile&lt;/span&gt; &lt;span class="mi"&gt;65535&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="s"&gt;epoll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;multi_accept&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;keepalive_timeout&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;keepalive_requests&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;tcp_nopush&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;tcp_nodelay&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're proxying to PHP-FPM over a Unix socket, bump the backlog to match expected concurrency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/run/php/php8.3-fpm.sock&lt;/span&gt;
&lt;span class="py"&gt;listen.backlog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1024&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fix 3: your database connections are too expensive
&lt;/h2&gt;

&lt;p&gt;Each unpooled connection costs a handshake, auth round trip, and memory allocation. At scale, this is a silent killer. For Postgres, PgBouncer is the standard fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;pgbouncer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[databases]&lt;/span&gt;
&lt;span class="py"&gt;yourapp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;host=127.0.0.1 port=5432 dbname=yourapp&lt;/span&gt;

&lt;span class="nn"&gt;[pgbouncer]&lt;/span&gt;
&lt;span class="py"&gt;listen_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;6432&lt;/span&gt;
&lt;span class="py"&gt;auth_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;md5&lt;/span&gt;
&lt;span class="py"&gt;pool_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;transaction&lt;/span&gt;
&lt;span class="py"&gt;max_client_conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;
&lt;span class="py"&gt;default_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your app at port 6432 instead of 5432. This typically cuts connection overhead by 60-80% under concurrent load. MySQL users: look at ProxySQL or mysqlnd_mux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4: cache what shouldn't hit the database every time
&lt;/h2&gt;

&lt;p&gt;Install Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;redis-server
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;redis-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set eviction policy so it doesn't just run out of memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;maxmemory&lt;/span&gt; &lt;span class="err"&gt;512mb&lt;/span&gt;
&lt;span class="err"&gt;maxmemory-policy&lt;/span&gt; &lt;span class="err"&gt;allkeys-lru&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For custom apps, wrap expensive queries in a cache-aside pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"product:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM products WHERE id = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fix 5: stop static assets from hitting your app server
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(jpg|jpeg|png|gif|css|js|woff2)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;30d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;immutable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's a CDN in front, confirm it respects your origin headers instead of overriding with its own default TTL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying it actually worked
&lt;/h2&gt;

&lt;p&gt;Re-run the same load test, same concurrency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ab &lt;span class="nt"&gt;-n&lt;/span&gt; 1000 &lt;span class="nt"&gt;-c&lt;/span&gt; 50 https://yourdomain.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare against baseline. RPS should climb, often 30-100%. Response time variance between p50 and p95 should tighten. Failed requests should hit zero at your target concurrency.&lt;/p&gt;

&lt;p&gt;Watch PHP-FPM workers scale during load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s1"&gt;'ps aux | grep php-fpm | wc -l'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workers should scale up smoothly and settle, not spike immediately to &lt;code&gt;max_children&lt;/code&gt; and stay there. If they pin instantly, either your sizing is wrong or a slow query is holding connections open too long.&lt;/p&gt;

&lt;p&gt;Check PgBouncer is actually in the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-p&lt;/span&gt; 6432 &lt;span class="nt"&gt;-U&lt;/span&gt; youruser yourapp &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SHOW POOLS;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check Redis hit rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli info stats | &lt;span class="nb"&gt;grep &lt;/span&gt;keyspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Healthy cache-aside logic should show above 80% hit ratio within minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes that undo all of this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oversizing max_children.&lt;/strong&gt; More workers than RAM supports causes swapping, which is worse than queued requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching without invalidation.&lt;/strong&gt; Stale cached data is a harder bug to catch than a slow endpoint. Always set a TTL or invalidate on write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pooling without adjusting app-level assumptions.&lt;/strong&gt; Transaction-mode pooling breaks session-level features like advisory locks if your app still expects a persistent connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the baseline.&lt;/strong&gt; No before/after numbers means no proof anything worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a full re-architecture. It's five configuration changes applied in order, with measurement at each step. That's usually enough to take a server from falling over at the first spike to handling concurrency predictably.&lt;/p&gt;

&lt;p&gt;Full walkthrough with more context: &lt;a href="https://binadit.com/blog/website-server-performance-managed-cloud-provider-europe-setup" rel="noopener noreferrer"&gt;How to set up website server performance that holds under real traffic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/website-server-performance-managed-cloud-provider-europe-setup" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your email is GDPR-compliant today. Will it still be next year?</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Sun, 20 Sep 2026 13:07:11 +0000</pubDate>
      <link>https://dev.to/binadit/your-email-is-gdpr-compliant-today-will-it-still-be-next-year-3o5b</link>
      <guid>https://dev.to/binadit/your-email-is-gdpr-compliant-today-will-it-still-be-next-year-3o5b</guid>
      <description>&lt;h2&gt;
  
  
  Your GDPR compliance depends on a court case you're not tracking
&lt;/h2&gt;

&lt;p&gt;Here's an uncomfortable fact: the legal basis for your Google Workspace or Microsoft 365 email is a single framework that's currently being challenged in front of the same court that already killed its two predecessors. If you run infrastructure or make hosting decisions, this is worth ten minutes of your time.&lt;/p&gt;

&lt;p&gt;Ask your IT partner if EU email in a US productivity suite is GDPR-compliant and you'll get a confident yes: both providers are certified under the EU-US Data Privacy Framework. Technically true. Also missing the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  The legal stack, in short
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sending personal data to a US company is a "transfer" under GDPR Chapter V (Articles 44-50).&lt;/li&gt;
&lt;li&gt;That's only lawful today because of the EU-US Data Privacy Framework (DPF), adopted July 2023.&lt;/li&gt;
&lt;li&gt;Its two predecessors, Safe Harbor and Privacy Shield, were both struck down by the Court of Justice of the EU (CJEU).&lt;/li&gt;
&lt;li&gt;The DPF is now under appeal at that same court.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three attempts. Two collapses. One pending verdict. That's not a stable foundation to build a compliance program on.&lt;/p&gt;

&lt;h3&gt;
  
  
  "We use an EU data center" doesn't fix it
&lt;/h3&gt;

&lt;p&gt;This is the part engineers get wrong most often. Region selection is not jurisdiction. Under the US CLOUD Act, a US company has to hand over data it controls when a US authority demands it, regardless of which data center it's sitting in. A Frankfurt or Amsterdam region owned by a US parent is still in scope.&lt;/p&gt;

&lt;p&gt;Don't take my word for it. In June 2025, Microsoft France's director of legal affairs testified under oath before a French Senate inquiry. Asked directly if he could guarantee French citizens' data would never be handed to the US government without French authorization, his answer was: "No, I cannot guarantee that."&lt;/p&gt;

&lt;p&gt;That's the provider, on the record, under oath.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where things stand right now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A challenge to the DPF (Latombe v. Commission) was dismissed by the General Court in September 2025, but only on facts as they existed in 2023. Nothing since then was considered. It's now on appeal to the CJEU.&lt;/li&gt;
&lt;li&gt;Microsoft was granted leave to intervene in that appeal. A company doesn't intervene in EU court proceedings unless it has serious skin in the game.&lt;/li&gt;
&lt;li&gt;The US oversight board that underpins the DPF (PCLOB) has been without quorum since January 2025.&lt;/li&gt;
&lt;li&gt;In June 2026, the US Supreme Court ruled FTC commissioners can be removed at will by the President. The FTC is the enforcement body for the DPF on the US side.&lt;/li&gt;
&lt;li&gt;FISA Section 702, the surveillance law at the center of the original Schrems II ruling, lapsed in June 2026 after failed extensions. Collection continues under existing court certifications regardless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means the DPF will definitely fall. It means your compliance posture depends on a decision you don't control, on a timeline you can't predict. Last time this happened (Schrems II, July 2020), companies had zero notice and a three-year gap before a replacement existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI layer adds more surface area
&lt;/h3&gt;

&lt;p&gt;Copilot and Gemini are now embedded directly in mail and file storage. That's more data flows to account for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Questions worth asking your provider contract, literally:&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Does input get used for model training? (check tier, not marketing page)
&lt;span class="p"&gt;-&lt;/span&gt; Can you delete a specific individual's data from a trained model? (usually no)
&lt;span class="p"&gt;-&lt;/span&gt; Do you know which model processed which record, in which region?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EU AI Act's transparency rules are already active (since August 2026). The heavier documentation requirements for HR, credit, and access-to-services use cases were pushed to December 2027, but that's a delay, not a way out. If you can't answer the questions above today, you won't be able to answer them then either.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual fix
&lt;/h3&gt;

&lt;p&gt;If email, calendar, and file storage sit with a provider incorporated and operating entirely in the EU, no CLOUD Act exposure exists because there's no US legal entity to compel. That's not a mitigation, it's the absence of the transfer problem entirely.&lt;/p&gt;

&lt;p&gt;As a data point: a Dutch provider offering this setup starts at €1.99 per mailbox per month, often cheaper than what teams already pay for Workspace or M365 licensing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical takeaway
&lt;/h3&gt;

&lt;p&gt;Check who actually handles mail for your domain (MX records will tell you the provider, not the jurisdiction, but it's a start). Then ask your provider the same question the French Senate asked Microsoft: can you guarantee this data is never handed over without our government's consent? If the answer is "no, but it hasn't happened," that's not a compliance guarantee, that's a probability bet.&lt;/p&gt;

&lt;p&gt;Full breakdown of the legal timeline and case law: &lt;a href="https://binadit.com/blog/email-gdpr-compliant-google-workspace-microsoft-365" rel="noopener noreferrer"&gt;Your email is GDPR-compliant today. Will it still be next year?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/email-gdpr-compliant-google-workspace-microsoft-365" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gdpr</category>
      <category>googleworkspace</category>
      <category>microsoft365</category>
      <category>cloudact</category>
    </item>
    <item>
      <title>Solving the deploy frequency wall: from weekly releases to multiple daily deploys without new incidents</title>
      <dc:creator>binadit</dc:creator>
      <pubDate>Fri, 18 Sep 2026 07:59:30 +0000</pubDate>
      <link>https://dev.to/binadit/solving-the-deploy-frequency-wall-from-weekly-releases-to-multiple-daily-deploys-without-new-4i8h</link>
      <guid>https://dev.to/binadit/solving-the-deploy-frequency-wall-from-weekly-releases-to-multiple-daily-deploys-without-new-4i8h</guid>
      <description>&lt;h1&gt;
  
  
  Why your team keeps reverting to weekly deploys (and how to actually fix it)
&lt;/h1&gt;

&lt;p&gt;Here's a pattern that shows up constantly in teams of 15-40 engineers: leadership pushes for faster releases, the team bumps deploy frequency up, and within a few weeks someone quietly walks it back to weekly. No mandate, no meeting. Just two or three rough releases in a row, and the team self-polices back to "safe" cadence.&lt;/p&gt;

&lt;p&gt;It looks like a discipline problem. It isn't. It's an infrastructure problem, and it's fixable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real issue: your pipeline was built for a different era
&lt;/h2&gt;

&lt;p&gt;When you deploy weekly, you can afford sloppiness because a human has time to babysit each release. Watch the error rate for 20 minutes, glance at a dashboard, ship it. Try that five times a day and the manual check either disappears or becomes theater nobody trusts.&lt;/p&gt;

&lt;p&gt;Under the hood, this usually comes down to four coupled problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploys aren't atomic.&lt;/strong&gt; Code, migrations, and config all ship in one shot. If the migration is slow, the code can't go out without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback means redeploying.&lt;/strong&gt; Reverting takes the same pipeline, the same time, the same risk as deploying forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring lags deploy speed.&lt;/strong&gt; Alert thresholds and log delays were tuned for hourly checks, not minute-by-minute validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blast radius is 100%.&lt;/strong&gt; Every deploy hits the whole fleet at once, so any bad release is automatically a full incident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is about code quality or test coverage. It's about the deploy path, routing layer, and monitoring stack not being built for the frequency you're asking of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: decouple, automate rollback, shrink the blast radius
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Split schema changes from code deploys
&lt;/h3&gt;

&lt;p&gt;Migrations are usually the biggest source of deploy anxiety. Use the expand/contract pattern so schema changes never ship in the same step as code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Step 1: expand (safe, backward compatible)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;shipping_method_v2&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Step 2: dual-write in app code, backfill&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;shipping_method_v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shipping_method&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;shipping_method_v2&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Step 3: contract (separate deploy, days later)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;shipping_method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code deploys stop waiting on migrations, and migrations stop blocking rollbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Make rollback a routing flip, not a redeploy
&lt;/h3&gt;

&lt;p&gt;If reverting means rerunning the pipeline backward, people will hesitate to ship under pressure. Rollback should be a traffic switch, done in seconds:&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="c1"&gt;# Nginx upstream weight shift for canary rollback&lt;/span&gt;
&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;app-v124-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="s"&gt;weight=0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# new version, was live&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;app-v123-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="s"&gt;weight=100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# previous stable, restored&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# reload takes effect in under 1 second&lt;/span&gt;
&lt;span class="k"&gt;nginx&lt;/span&gt; &lt;span class="s"&gt;-s&lt;/span&gt; &lt;span class="s"&gt;reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or on Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout undo deployment/checkout-api
kubectl rollout status deployment/checkout-api &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ship to a small slice of instances first. Healthy? Shift more weight over. Not healthy? Shift back to zero. No twelve-minute rebuild required to recover.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automate the canary check
&lt;/h3&gt;

&lt;p&gt;A human staring at Grafana for 20 minutes works at weekly cadence. At five deploys a day, it's neither sustainable nor reliable, people get worse at spotting anomalies the more times they repeat the check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;canary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error_rate&lt;/span&gt;
      &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baseline + 0.5%&lt;/span&gt;
      &lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;p95_latency&lt;/span&gt;
      &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baseline + 15%&lt;/span&gt;
      &lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5xx_count&lt;/span&gt;
      &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baseline + &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;action_on_fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto_rollback&lt;/span&gt;
  &lt;span class="na"&gt;promotion_steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;5%&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;25%&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;50%&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;100%&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;step_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably the single highest-leverage change for teams moving to daily deploys. It turns a subjective judgment call into a repeatable gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Shrink the blast radius with progressive rollout
&lt;/h3&gt;

&lt;p&gt;Deploying to 100% of instances at once makes every release a full-fleet bet. Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ship to 5% of instances or one availability zone&lt;/li&gt;
&lt;li&gt;Hold through a full traffic cycle, including cron jobs and batch work&lt;/li&gt;
&lt;li&gt;Auto-promote on healthy metrics, auto-rollback on bad ones&lt;/li&gt;
&lt;li&gt;Only reach 100% after clearing every stage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same principle as safe database migrations: keep each irreversible step small.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to know it actually worked
&lt;/h2&gt;

&lt;p&gt;Don't just track "deploy success." Watch these specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change failure rate&lt;/strong&gt;: percentage of deploys triggering a rollback or hotfix. Aim under 15% (DORA elite benchmark), trending down as frequency goes up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MTTR&lt;/strong&gt;: with automated rollback via traffic weight shift, this should drop under 5 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback execution time&lt;/strong&gt;: measure from "threshold breached" to "traffic fully reverted." Should be seconds to low minutes, not tied to build time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canary false negative rate&lt;/strong&gt;: how often something bad slips past the gate and gets caught by users instead. If it happens more than once a quarter, retune thresholds, don't remove the gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy frequency vs. incident count&lt;/strong&gt;: these two lines should decouple. If incidents still climb with frequency, your canary metrics are probably missing a business signal like checkout completion or cart abandonment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give this at least four weeks at the new cadence before declaring victory. One good week proves nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it from backsliding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Alert on deploy frequency itself. A quiet drop without an explicit decision usually means the pipeline is causing avoidance.&lt;/li&gt;
&lt;li&gt;Treat canary thresholds as living config, not a one-time setup. Traffic patterns shift, thresholds need to shift with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full technical breakdown, including the zero-downtime migration mechanics: &lt;a href="https://binadit.com/blog/infrastructure-performance-optimization-multiple-daily-deploys" rel="noopener noreferrer"&gt;read the original article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://binadit.com/blog/infrastructure-performance-optimization-multiple-daily-deploys" rel="noopener noreferrer"&gt;binadit.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
