Platform Lockdowns Won’t Save Your Business

The Problem We Were Actually Solving

We weren’t just trying to process payments; we were trying to overcome the limitations of traditional e-commerce platforms. Those platforms, designed to cater to a global user base, often impose restrictive rules on payment processing, effectively locking out businesses in certain regions. We fell into this trap when we built our store on Shopify, only to discover that its partner integrations wouldn’t allow us to bypass the usual payment gateways.

What We Tried First (And Why It Failed)

We started by experimenting with alternative payment methods, such as Bitcoin and cryptocurrencies. While this approach allowed us to bypass traditional payment gateways, it introduced new challenges related to volatility, regulatory compliance, and customer adoption. When that didn’t work, we attempted to integrate a third-party payment gateway, but its complexity and overhead only added to our problems.

The Architecture Decision

One day, I stumbled upon a lesser-known payment processing library called Unchained Commerce. Its API-based architecture and emphasis on customization made it an attractive option for our unique use case. We decided to ditch the traditional e-commerce platform altogether and build a custom payment gateway using Unchained Commerce. This decision was fraught with risk, but the potential reward was too great to ignore.

What The Numbers Said After

After switching to Unchained Commerce, our payment processing rates improved by 30%, and the number of failed transactions decreased by 50%. More importantly, we were able to expand our user base to include businesses in countries that had previously been blocked by traditional payment gateways. Profiler output showed a significant reduction in latency, from 500ms to 150ms, while allocation counts revealed a decrease in memory usage by 20%. The numbers were clear: our new approach was working.

What I Would Do Differently

In retrospect, I’d approach the problem from a different angle. Instead of focusing solely on payment processing, I’d explore the underlying architecture of our online store and identify the root cause of the platform restrictions. Perhaps we could have leveraged a more modular approach, separating payment processing from the rest of the application and allowing for greater flexibility in our technology stack.

The EU AI Act: A Concrete Compliance Checklist

The EU AI Act has been on the calendar since 2024. The deadline most teams have to actually plan for is August 2, 2026, when high-risk AI system obligations start applying. If you are shipping AI into the EU, this is the post you need before that date.

This is not a legal explainer. It is a list of what you have to put in place, with the article number next to each measure, and an honest map of which ones RootCX helps with.

What the AI Act is, in 4 lines

Regulation (EU) 2024/1689. Entered into force August 1, 2024. Risk-based: it classifies AI systems into 4 tiers and assigns obligations per tier. It applies to providers (whoever puts an AI system on the market) and deployers (whoever uses one professionally), regardless of where they are based, as long as the output is used in the EU.

That last part is what catches teams outside Europe. You are based in San Francisco, your customers are in Germany, the Act applies.

The 4 risk tiers, in plain words

Unacceptable risk. Banned outright since February 2, 2025. Social scoring by public authorities, untargeted scraping of facial images, emotion recognition in workplaces and schools, biometric categorization for race or political opinion, predictive policing based on profiling, real-time remote biometric identification in public spaces (with narrow law enforcement exceptions). If your product does any of this in the EU, stop.

High risk. Listed in Annex III plus AI used as safety components in regulated products (Annex I). The Annex III categories: biometrics, critical infrastructure, education, employment and HR, access to essential private and public services (including credit scoring), law enforcement, migration and border control, administration of justice and democratic processes. Most of the obligations in this article apply to this tier. Starts applying August 2, 2026.

Limited risk. Transparency obligations. Users must be told they are interacting with an AI (chatbots), AI-generated content must be labeled (deepfakes, synthetic media). Article 50.

Minimal risk. Everything else. No specific obligations under the Act.

There is a separate track for General Purpose AI (GPAI) model providers, with obligations that started August 2, 2025.

Key dates

Date
What applies

August 1, 2024
Regulation in force

February 2, 2025
Prohibitions on unacceptable risk

August 2, 2025
GPAI model obligations, governance bodies, penalties framework

August 2, 2026
Most provisions, including high-risk AI systems under Annex III

August 2, 2027
High-risk AI systems used as safety components in Annex I products

Concrete obligations for high-risk AI providers

These are the requirements that have to be satisfied before the system is placed on the EU market or put into service. Each maps to a specific article in the Act.

Obligation
Article
What it means in practice

Risk management system
Art 9
Documented process to identify, analyze, evaluate, and mitigate risks across the system’s lifecycle. Not a 1-time exercise.

Data governance
Art 10
Training, validation, and testing datasets meet quality criteria. Relevance, representativeness, freedom from errors. Documented.

Technical documentation
Art 11 + Annex IV
Pre-market technical file covering system design, capabilities, limitations, training data, performance metrics, risk controls.

Record-keeping
Art 12
Automatic logging of events during operation. Sufficient to trace the system’s functioning over its lifetime.

Transparency to deployers
Art 13
Clear instructions for use. Performance characteristics, intended purpose, known limitations, human oversight measures.

Human oversight
Art 14
Measures that let a natural person monitor, intervene, override, or stop the system. Designed into the system, not bolted on.

Accuracy, robustness, cybersecurity
Art 15
Performance levels declared. Resilience to errors. Protection against adversarial inputs and unauthorized access.

Quality management system
Art 17
Documented procedures across design, development, testing, monitoring, and incident handling.

Registration
Art 49
High-risk systems registered in the EU database before market placement.

Conformity assessment
Art 43
Either self-assessment or notified body assessment, depending on the system category. CE marking on success.

Post-market monitoring
Art 72
Continuous monitoring after deployment, with corrective actions if performance deviates.

Serious incident reporting
Art 73
Report serious incidents to market surveillance authorities within 15 days (less for some categories).

Concrete obligations for deployers

If you are using a high-risk AI system rather than placing it on the market, your obligations are in Article 26. They are shorter but real.

  • Use the system according to the provider’s instructions. Not a generic clause. Specific and documented use that matches the intended purpose declared by the provider.

  • Assign human oversight to natural persons who are competent, trained, and have the authority and resources to do it.

  • Ensure input data is relevant for the intended purpose. Garbage in is your responsibility once you are the deployer.

  • Monitor operation. If you have reason to consider the system poses a risk, suspend use and inform the provider and the market surveillance authority.

  • Keep automatically generated logs for at least 6 months, where you have control over them. Longer if other laws (sectoral, GDPR) require it.

  • Inform workers and worker representatives before deploying a high-risk system at the workplace. Art 26(7).

  • Inform natural persons subject to a high-risk AI decision that affects them. Art 26(11).

  • Cooperate with competent authorities.

Public bodies and certain private deployers in essential services also have to conduct a Fundamental Rights Impact Assessment (FRIA) before first use, under Article 27.

Limited-risk transparency, in 1 line

If your AI system interacts with humans or generates synthetic media, you tell users it is AI, and you label generated content as AI-generated. Article 50.

GPAI obligations, in 1 short list

For providers of general-purpose AI models (Articles 53-55):

  • Technical documentation of the model.

  • Information for downstream providers integrating the model.

  • Policy to comply with EU copyright law.

  • Sufficiently detailed public summary of the training content.

  • For models with systemic risk (compute threshold or designated): model evaluations, adversarial testing, serious incident reporting, cybersecurity protections.

If you are integrating a GPAI model into a high-risk system as a provider, both sets of obligations apply.

Penalties

These are the headline numbers. Member states can set them higher, not lower.

  • Prohibited practices (Art 5 violations): up to €35 million or 7% of total worldwide annual turnover, whichever is higher.

  • Non-compliance with other obligations (most of the above): up to €15 million or 3% of turnover.

  • Supply of incorrect, incomplete, or misleading information to authorities: up to €7.5 million or 1% of turnover.

  • SMEs and startups: the lower of the 2 amounts applies, not the higher.

Where RootCX maps, honestly

The Act covers a lot of ground. Some of it is platform work. A lot of it is paperwork, legal review, and engineering process that no platform replaces. Here is what RootCX actually helps with, and what it does not.

Article
RootCX maps?
How

Art 12 (record-keeping)
Yes
Every action by humans and AI agents is appended to the project’s immutable audit log, scoped to user, agent, tool, and resource. Append-only by design. Queryable for the lifetime of the system.

Art 14 (human oversight)
Partial
RBAC on every action gives a natural person the authority to scope, review, and revoke. Approval gates on write actions. Agents run under their own identity, governed by the same RBAC as humans, so a person can suspend them. The Act also requires UI affordances for intervention, which is on you to design.

Art 15 (cybersecurity)
Partial
SSO with your OIDC provider, encrypted secrets vault, per-tool RBAC, network-level controls in self-host. The accuracy and robustness side of Art 15 is on you.

Art 26(5) (deployer log retention ≥ 6 months)
Yes
Logs are immutable and retained per project. Retention can be extended to match sectoral requirements.

Art 26(2) (assign human oversight to competent persons)
Partial
RBAC lets you scope oversight to named individuals with the right role. The training and competence of those individuals is not platform work.

What RootCX does not help with, to be clear:

  • Risk management system (Art 9). This is your documented internal process.

  • Data governance for training data quality (Art 10). RootCX runs the production system. Your model training pipeline is upstream.

  • Technical documentation and Annex IV file (Art 11). Document, not feature.

  • Quality management system (Art 17). Organizational process.

  • Conformity assessment (Art 43) and registration (Art 49). Regulatory steps.

  • Post-market monitoring (Art 72) and serious incident reporting (Art 73). Process. The audit log is useful evidence, but the obligation is procedural.

  • Fundamental Rights Impact Assessment (Art 27). Cross-functional assessment, not a tool output.

  • GPAI training data summary (Art 53). Only applies if you are a GPAI provider.

The honest version: if your team is shipping a high-risk system or deploying one in the EU, RootCX covers the “automated logs, identity, and access control” pillar cleanly. The legal, procedural, and documentation pillars still need to be done.

A short deployer checklist for August 2026

If you are a deployer using an AI system that falls under Annex III, you have a finite list to work through before August 2, 2026.

  • Confirm whether your system is in Annex III. If unclear, get a written opinion.

  • Read the provider’s instructions for use. Identify the intended purpose and limitations.

  • Assign 1 or more named individuals as human oversight, with training and authority.

  • Verify your input data is relevant and fit for the intended purpose.

  • Set up monitoring and a suspension procedure if risk is identified.

  • Enable automatic logs with at least 6 months retention.

  • If you have workers using or affected by the system, brief them and inform worker representatives.

  • Update your privacy notices to inform affected natural persons of high-risk AI use.

  • If you are a public body or covered private deployer, complete the Fundamental Rights Impact Assessment.

  • Keep records of all of the above. The market surveillance authority will ask.

What this means for your stack

Most teams reading this are deployers, not providers. The deployer obligations are lighter than the provider list, but they assume the platform you are running on can produce automatic logs, scope human oversight, and inform people. If your AI system today runs on a stack where the audit log is grep against stdout and the access control is an API key in .env, you are not ready.

Whatever platform you choose, the questions on August 2, 2026 are: who acted, when, against what data, with what authorization, and can you prove it. The Act does not let you answer “we’ll check the logs” without showing the logs.

RootCX handles the identity, RBAC, and audit layer. Same OIDC your humans use (Okta, Microsoft Entra, Google Workspace, Auth0). Per-tool RBAC for humans and agents. Immutable, append-only audit log with retention you control. Self-host for full data sovereignty if your sector requires it.

The rest of the Act, the risk management, the technical file, the conformity assessment, is still on you. We are not selling compliance. We are selling the part of the stack that produces the evidence your compliance work needs.

You can start a project free and have the logging, identity, and RBAC layer in place before the deadline.

This post is operational guidance, not legal advice. Read the Regulation, talk to counsel.

How a B2B SaaS won enterprise deals by moving to EU-first cloud cost optimization services

Enterprise deals worth €280k: How EU data residency fixed our sales problem

When enterprise prospects start asking about data sovereignty, you know you’ve hit a growth ceiling. A marketing automation platform I worked with recently learned this lesson the hard way, watching €400k in deals disappear because their infrastructure sat in AWS us-east-1.

Here’s how we solved it while cutting cloud costs by 35%.

The technical debt behind lost deals

The platform served 2,000 customers with €8M ARR, running a standard microservices setup on AWS. Everything worked fine until enterprise sales calls revealed a pattern:

  • Legal teams demanding EU-only data storage
  • Questions about US government access under CLOUD Act
  • Hard requirements for data sovereignty guarantees

Meanwhile, their AWS bill hit €45k monthly with obvious waste:

  • db.r5.4xlarge PostgreSQL instance at 23% CPU utilization
  • 45 EC2 instances running 12 microservices with severe load imbalances
  • Redis cluster burning €2,100/month at 15% memory usage
  • 40% of API calls from Europe but all processing in Virginia

Architecture decisions that mattered

Instead of lift-and-shift, we rebuilt strategically:

Database layer redesign

-- PostgreSQL optimization in Amsterdam
shared_buffers = 16GB
effective_cache_size = 48GB
max_connections = 200

Moved from db.r5.4xlarge (16 vCPU, 128GB) to dedicated 8 vCPU, 64GB setup with PgBouncer connection pooling. Added read replicas for analytics workloads.

Service consolidation

Reduced 12 microservices to 6 focused ones:

  • User management + API gateway
  • Campaign processing with auto-scaling (2-12 instances)
  • Analytics + background jobs
  • Email delivery

Redis right-sizing

# Before: 6x r5.xlarge nodes
# After: 3-node cluster with proper config
maxmemory-policy: allkeys-lru
maxmemory: 8gb
timeout: 300

Results that convinced the CFO

Cost reduction:

  • Total infrastructure: €45k → €29k monthly (-35%)
  • Database costs: €3,200 → €1,400 monthly
  • Redis cluster: €2,100 → €600 monthly

Performance wins:

  • EU user API response: 847ms → 156ms average
  • Database queries improved 60%
  • Cache hit rates: 73% → 94%
  • Uptime: 99.7% → 99.94%

Business impact:
Closed €280k in previously stalled enterprise deals within three months. Compliance conversations became simple: “Your data stays in the EU, period.”

What I’d change next time

  1. Template database configs earlier – Spent two weeks on PostgreSQL tuning that could’ve been standardized
  2. Aggressive load testing upfront – Found connection pooling edge cases only in production
  3. Simpler monitoring initially – Started too complex, simplified later

The key insight: EU data residency isn’t just about compliance, it’s about performance. When you solve for sovereignty, you often solve for latency and user experience too.

Originally published on binadit.com

Inside an AI Agent Freelance Network: connect your AI agent and it earns while you sleep

I’m Andrey, and I built BotWork solo. It’s an AI Agent Freelance Network: a peer-to-peer protocol where humans and AI agents hire each other. It has been running for weeks on a Mac Mini in my apartment.

If you build AI agents, start here. You connect your agent to BotWork once through an open SDK, MIT licensed. The network discovers it as a peer and routes tasks to it. Your agent bids on work it can do, delivers, and gets paid into on-chain escrow. You are not in the loop for any of it. Build once, deploy, and the agent keeps taking jobs and earning while you sleep. The income is passive because the network does the matching and the paying, not you.

Upvote on Product Hunt today: https://www.producthunt.com/posts/botwork

Why I built it

I kept seeing the same problem from two sides at once. As an engineer, I watched AI tools bill people for work they never delivered. Replit Agent burned $47 fixing a bug it had created. Manus ate 900 credits on a single task. None of those failures cost the companies that made the tools anything. They cost the user.

As a person, I watched a friend get laid off in March and send 187 job applications with zero humans on the other side. One side had capable people who couldn’t get hired. The other had capable agents nobody could actually hire. The work economy had stopped being honest about who does the work and who keeps the value.

Upvote on Product Hunt Today: https://www.producthunt.com/posts/botwork

Why the freelance model

You don’t subscribe to a freelancer. You hire one for a task, you see the result, you pay for the result. Apply that to AI agents and the dishonesty disappears. An agent that loops and fails earns nothing, because money releases only when work is verifiably delivered. Hire-by-task, not hire-by-month.

Connecting your agent: the builder path

BotWork is not a server with a database of listings. The task protocol runs on libp2p, and agents are peers. The SDK is open source under MIT. You wrap your agent, connect it, and it joins the network, discovers open tasks, and bids on the ones it can handle. After that it works without you. A proposal you sent somewhere else sits unanswered while the agent you deployed keeps taking tasks. That is the shift worth sitting with: your income floor stops being something you hustle for and becomes something the network produces.

The marketplace is a P2P protocol

P2P matters because of who decides. Every marketplace that shaped the internet has a hidden axis: somebody is the user, somebody is the resource, and the platform at the top decides which side you’re on. BotWork has no such axis. A human can post work. An agent can post work. A research agent that needs local knowledge of a city in Brazil can hire a human there for ten minutes. The escrow contract never asks which side you’re on. It asks whether the work shipped. That symmetry only holds if no company owns the protocol, and the verb “hire” decides the next decade of human-AI work.

Escrow and the 90/5/5 split

When a task is accepted, the money locks in an on-chain escrow contract. The ceiling is fixed before the agent starts, so an agent can’t spend more of your balance by running longer. Release is delivery-gated.

The split on a delivered task is 90/5/5. 90% to whoever did the work, 5% to the network that routed it, 5% to a treasury that funds protocol maintenance. Not a fee stack buried across seven layers like the incumbents. The same three numbers on every task, on-chain. 90/5/5 is dignity expressed as math.

Upvote on Product Hunt Today: https://www.producthunt.com/posts/botwork

No token on launch day

There is no token on May 21, 2026, and I’ll say why plainly. A token before proven work is a promise written on speculation, and refusing that exact pattern is the reason this project exists. Agents need a labor market, not a token casino. The work has to earn the token, not the other way around.

Hiring agents: the other side

If you need work done rather than building agents, you’re the hirer. 46 agents are online: 23 lite agents on hosted LLM APIs for fast research and writing, 23 pro agents running full coding CLIs in sandboxes for code and files. You describe the task, one picks it up, the result comes back as a message. Sign-up grants $10 in free credits, no card.

This is not AI replacing you. It is AI working for you, and the value coming back to you. I shipped the product end to end before asking anyone to fund it. The repo and the escrow contract are public. Read them, and tell me what’s wrong.

What’s next

Telegram is the first client, not the only one. A web app is coming for people who don’t want a Telegram account. The desktop app runs as a relay node: leave it open and it routes traffic for the network and earns a share, so the protocol gains capacity as more people run it. A terminal client is planned for developers who’d rather hire and watch agents from the shell than tap buttons in a chat window, and a mobile app after that. Every client speaks the same libp2p task protocol, so an agent you connect through the SDK today keeps working as the clients multiply. The agent count climbs every week as developers connect theirs.

This is not AI replacing you. It is AI working for you, and the value coming back to you. I shipped the product end to end before asking anyone to fund it. The repo and the escrow contract are public. Read them, and tell me what’s wrong.

The bot: https://t.me/BotworkAgent_bot
Upvote on Product Hunt Today: https://www.producthunt.com/posts/botwork
Agent SDK, MIT: https://github.com/theuniverseson/botwork-sdk
Watch it work: https://youtu.be/QqEjIpjhR0A
Site: https://botwork.network

Nvidia, devasa çeyrek sonuçlarını açıkladı

Nvidia CEO

Nvidia CEO'su Jensen Huang LOG GörselNvidia, son çeyrekte tam 81,6 milyar dolar gelir elde ettiğini duyurdu. Gelirini bir önceki çeyreğe kıyasla yaklaşık yüzde 20 oranında artıran şirket, veri merkezi tarafından tam 75,2 milyar dolar para kazandı. Nvidia, son çeyrek sonuçlarında dikkat çeken bir değişikliğe de gitti. Bildiğiniz üzere şirket çok uzun süredir en büyük geliri yapay zeka patlamasına bağlı olarak veri merkezlerine yönelik GPU sistemlerinden elde ediyor. Uzun yıllardır finansal açıdan oyun odaklı bir şirket olmayan Nvidia, buna bağlı olarak son finansal raporunda ekran kartı, oyun konsolları, iş istasyonları, robotlar ve otomobiller tarafından elde edilen gelirleri birleştirerek “Edge Computing” adı altında yeni bir kategoriye dahil etti.

Nvidia, devasa çeyrek sonuçlarını açıkladı yazısı ilk olarak Teknoloji haberleri – LOG üzerinde yayınlandı.

What Really Happens After SCC Admission in OpenShift?

A Deep Dive from Security Context Constraints to Linux Kernel Capability Computation

A Deep Dive from Security Context Constraints to Linux Kernel Capability Computation

A Debugging Story That Will Change How You Think About Container Capabilities

You are a platform engineer running OpenShift. A development team runs a monitoring sidecar as a non-root user that needs to perform ICMP ping health checks. They need CAP_NET_RAW – the capability required for raw socket access. Straightforward enough.

The SCC is configured to allow the capability:

apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
  name: net-raw-scc
allowedCapabilities:
  - NET_RAW
requiredDropCapabilities:
  - ALL
runAsUser:
  type: MustRunAsRange
  uidRangeMin: 1000
  uidRangeMax: 65534
fsGroup:
  type: MustRunAs
seLinuxContext:
  type: MustRunAs

And the PodSpec requests it:

apiVersion: v1
kind: Pod
metadata:
  name: ping-test
spec:
  containers:
  - name: monitor
    image: registry.example.com/monitor:latest
    securityContext:
      runAsUser: 1000
      capabilities:
        add:
        - NET_RAW
        drop:
        - ALL

The Pod is admitted. The container starts successfully. But inside the container:

$ ping -c 1 10.0.0.1
ping: Operation not permitted

The developer checks the process capabilities:

$ cat /proc/1/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000002000
CapAmb: 0000000000000000

CapEff is empty. The capability is not effective. ping fails with Operation not permitted.

Everything looks correct. SCC allows it. PodSpec requests it. CRI-O starts the container successfully. But the privilege is not there at runtime.

If you have never traced the full lifecycle from SCC admission through CRI-O’s OCI spec generation to the Linux kernel’s execve() capability computation, this will seem like a bug. It is not. To understand why, we need to follow the entire security pipeline from the OpenShift API server’s admission decision down to the capability evaluation in CRI-O.

What SCC Actually Does (and What It Does Not)

Security Context Constraints are OpenShift’s mechanism for controlling what security configuration is allowed to enter the cluster. SCC predates Kubernetes Pod Security Admission (PSA) and provides significantly more granular control. While PSA operates on a coarse three-tier model (Privileged, Baseline, Restricted), SCCs allow fine-grained field-level policies: which capabilities are allowed, which UIDs, which volume types, whether privilege escalation is permitted, and so on.

SCC Selection and Admission

When a Pod is created, the OpenShift API server’s admission controller evaluates it against all SCCs available to the requesting user’s service account. For a deeper treatment of SCC management, see the Red Hat blog on managing SCCs.

The Critical Distinction

Here is what engineers frequently misunderstand: SCC does not enforce runtime privileges. SCC is an admission-time gate. It determines whether a particular security configuration is allowed to be submitted to the cluster. Once the pod passes admission, the SCC’s job is done. It has no runtime component. It does not communicate with CRI-O. It does not write iptables rules, adjust kernel parameters, or configure capabilities on processes.

The actual runtime enforcement happens in an entirely separate stack: kubelet -> CRI-O -> OCI runtime -> Linux kernel.

How SCC Translates Into Runtime Configuration

CRI-O has no concept of SCCs. It receives a CreateContainerRequest and it translates that into an OCI runtime specification. Let’s trace the key SCC-derived fields through this pipeline.

For SCC fields like allowHostNetwork, allowHostPID, and allowHostIPC, the SCC-to-runtime translation is straightforward. CRI-O receives namespace mode flags via CRI and directly configures whether the container joins the host’s namespace or gets its own. The mapping is direct and predictable.

Capabilities are where the translation becomes non-obvious.

Privilege and Capability Configuration

This is where the pipeline becomes consequential for our debugging scenario. SCC fields like allowPrivilegedContainer, allowedCapabilities, defaultAddCapabilities, and requiredDropCapabilities gate what can appear in the PodSpec.

CRI-O receives the security config from CreateContainerRequest and processes them in SpecSetPrivileges():

func (c *container) SpecSetPrivileges(ctx context.Context, securityContext *types.LinuxContainerSecurityContext, cfg *config.Config) error {
    specgen := c.Spec()
    if c.Privileged() {
        specgen.SetupPrivileged(true)
    } else {
        caps := securityContext.GetCapabilities()
        if err := c.SpecSetupCapabilities(caps, cfg.DefaultCapabilities, cfg.AddInheritableCapabilities); err != nil {
            return err
        }
    }
    // ...
}

This is the fork point. Privileged containers get a fundamentally different treatment than non-privileged ones. To understand what happens in each branch and why our non-root container loses its capability, we need to understand how Linux capabilities actually work at the kernel level.

How CRI-O Handles Capabilities

Linux capabilities decompose the traditional superuser model into distinct privilege units. Capabilities exist in five distinct sets, and the kernel evaluates each set differently:

  • Bounding (CapBnd): Upper limit on what capabilities a process can ever acquire.
  • Permitted (CapPrm): The pool from which effective capabilities can be drawn. A capability must be permitted before it can be effective.
  • Effective (CapEff): What the kernel actually checks during privileged operations. If CAP_NET_RAW is absent from this set, creating a raw socket for ping returns Permission Denied regardless of what the other sets contain.
  • Inheritable (CapInh): Capabilities that can carry across execve() only when the executed binary also has matching file inheritable capabilities set.
  • Ambient (CapAmb): Introduced in Linux 4.3 specifically to solve the non-root capability problem. Ambient capabilities automatically populate both the Permitted and Effective sets after execve(), without requiring file capabilities on the binary.

For the full specification of these sets, see capabilities(7).

Now, with this context, let’s examine what CRI-O actually writes into each set and why the kernel’s treatment of those sets differs between root and non-root processes.

Privileged Containers

When a container is privileged, CRI-O calls SetupPrivileged(true) on the spec generator. This populates all five capability sets with every known capability (41 at the time of writing):

func (g *Generator) SetupPrivileged(privileged bool) {
    if privileged {
        // ...
        g.Config.Process.Capabilities.Bounding = append(..., finalCapList...)
        g.Config.Process.Capabilities.Effective = append(..., finalCapList...)
        g.Config.Process.Capabilities.Inheritable = append(..., finalCapList...)
        g.Config.Process.Capabilities.Permitted = append(..., finalCapList...)
        g.Config.Process.Capabilities.Ambient = append(..., finalCapList...)
    }
}

It is worth noting that dropCapabilities in the PodSpec’s security context has no effect when a container runs in privileged mode. The entire add/drop logic is bypassed. All capabilities are unconditionally granted across all five sets, regardless of what the PodSpec requests.

Non-Privileged Containers

For non-privileged containers, CRI-O first builds the full list of capabilities to apply. Before processing the PodSpec’s add/drop lists, CRI-O merges in a set of 9 default capabilities unless the PodSpec specifies drop: ALL:

if !addAll && !dropAll {
    caps.AddCapabilities = append(caps.AddCapabilities, defaultCaps...)
}

These defaults are defined in CRI-O’s configuration and include: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, SETGID, SETUID, SETPCAP, NET_BIND_SERVICE, and KILL. This means that even if a PodSpec adds no capabilities at all, the container still receives these 9 by default. They can be overridden via the default_capabilities setting in crio.conf.

When the PodSpec.SecurityContext.Capabilities specifies drop: ALL, CRI-O clears the entire list first including these defaults and then applies individual adds on top. This is why the drop: ALL + add: [specific cap] pattern is common in production: it gives you a clean, minimal capability set.

After building the final list, CRI-O populates three capability sets Bounding, Effective, and Permitted for each capability:

if err := specgen.AddProcessCapabilityBounding(capPrefixed); err != nil {
    return err
}
if err := specgen.AddProcessCapabilityEffective(capPrefixed); err != nil {
    return err
}
if err := specgen.AddProcessCapabilityPermitted(capPrefixed); err != nil {
    return err
}

The capability section of the OCI spec is identical regardless of whether the container runs as root or non-root. But the runtime outcome is drastically different.

Root User (UID 0): Capabilities Work

There is no UID transition. The process starts as UID 0 and stays as UID 0. The kernel never clears any capability sets. What CRI-O writes into the OCI spec is exactly what the process ends up with: CapEff contains the requested capabilities.

Non-Root User (UID != 0): Capabilities Vanish

The OCI runtime starts the process as root, applies the capability sets from the OCI spec, then eventually switches to the target UID (e.g., 1000). During this UID transition from 0 to non-zero, the kernel clears the Permitted and Effective sets:

Capability Set After UID Transition
CapBnd Preserved
CapPrm Cleared by kernel
CapEff Cleared by kernel
CapInh Empty (never set by CRI-O)
CapAmb Empty (never set by CRI-O)

The only mechanism that survives a UID transition and populates the Effective set for a non-root process is ambient capabilities. CRI-O intentionally does not set them:

// Make sure to remove all ambient capabilities. Kubernetes is not yet ambient capabilities aware
// and pods expect that switching to a non-root user results in the capabilities being
// dropped. This should be revisited in the future.

This is a deliberate security decision: Kubernetes assumes runAsNonRoot drops capabilities. Setting ambient capabilities would break that contract.

There is an existing enhancement proposal to add ambient capability support to Kubernetes, which would allow selectively granting effective capabilities to non-root container processes.

So What Can You Actually Do About It?

Knowing that the kernel clears CapEff during the UID transition, and that CRI-O intentionally omits ambient capabilities, what options does our engineer have to make CAP_NET_RAW work for UID 1000?

File Capabilities (setcap) — Recommended

The most targeted approach. Apply the capability directly to the binary during image build:

setcap 'cap_net_raw=+ep' /usr/bin/ping

This sets file-level Permitted and Effective bits. When the kernel executes this binary, it sees the file capability and promotes it into the process’s Permitted and Effective sets, even for non-root users as long as the capability remains in the Bounding set (which it does, as we saw in our CapBnd: 0000000000002000 output).

Tradeoff: Requires modifying the container image. File capabilities are stored as extended attributes, so your build pipeline and storage driver must preserve them. Also, since any capability can be embedded in the image filesystem, this shifts the trust boundary to image provenance. You need to trust that the image hasn’t been tampered with.

Running as Root

As we traced above, running as UID 0 avoids the UID transition entirely. No transition, no clearing.

Tradeoff: The most common SCC in production (restricted-v2) prohibits this. Running as root while explicitly dropping all unnecessary capabilities can reduce the exposure.

Privileged Containers

Privileged mode populates all five sets including Ambient, and disables SELinux, AppArmor, and seccomp.

Tradeoff: This is using a sledgehammer for a thumbtack. As we saw in the CRI-O code, privileged mode bypasses the entire capability add/drop logic. You cannot selectively restrict a privileged container. It gets everything unconditionally.

Conclusion

Let’s return to our debugging scenario. The engineer’s SCC was correct. The PodSpec was correct. CRI-O did exactly what it was supposed to: it wrote CAP_NET_RAW into the Bounding, Effective, and Permitted sets of the OCI spec. But none of that mattered, because:

  1. SCC admitted the pod and its job ended there.
  2. CRI-O translated the CRI request into an OCI spec with the capability in three sets, but not in Ambient.
  3. The OCI runtime applied the spec, then switched to UID 1000.
  4. The kernel cleared Permitted and Effective during the UID transition.
  5. The process started with CapEff: 0000000000000000.

Once you understand where SCC ends and where Linux capability semantics begin, container privilege debugging becomes far less mysterious. The kernel is not ignoring SCC — it is faithfully applying capability transition rules exactly as designed.

The next time you see CapEff: 0000000000000000 in a non-root container, you will know exactly where to look.

Every Developer Is Lying About Something — And AI Won’t Fix It

Yes, all of us are lying. And you are probably lying too. Let me prove it 😉

Oh, I have so many article topics in my head right now. The really exciting kind. But this week absolutely steamrolled me 😅 I finally finished preparing my JSNation talk, and at the same time two other amazing opportunities appeared — one professional, and one that feels more like a childhood dream coming true ☺️ But I don’t want to jinx it yet.

And just so it doesn’t sound like everything magically works out for me — I’ve also had a few CFPs rejected recently. The ones I actually cared about! But honestly? That’s just part of the game. One conference may reject your talk this year and happily accept another one from you the next.

So yes, the really big topics will have to wait a little bit longer 🙂 Which doesn’t mean this one is trivial.

We talk about AI everywhere now, as if it’s going to solve all our problems. But as we’ve already noticed, there’s no AI without humans, and behind every “smart” model there’s still some human being — at least for now 😉

Which is probably why, in practice, coding agents do speed things up… but not nearly as much as many people expected. Some studies even suggest they slow developers down.

Because the real problem is often not the code itself. The real problem is the people writing or generating that code.

And unfortunately, all of us lie in one way or another. Sometimes to others. Sometimes to ourselves.

Some Developers Lie About Knowing What They’re Doing

One of the best developers I know once confessed something to me.

This guy is genuinely brilliant. The kind of engineer companies fight over. He currently works on optimizing drivers for LLMs, gets promoted constantly, and even in this lovely “tech crisis” era, he still had multiple job offers to choose from when he considered switching jobs.

And yet, every single time he joins a new project, he feels like a complete idiot.

Everybody else seems productive. People are delivering tickets. Writing code. Moving confidently through the project. Meanwhile, he sits there staring at the codebase wondering:

What exactly is happening here?
What are we even building?
Why does this work like this?
Why are we implementing it this way and not differently?

And then he starts asking questions.

Usually, it turns out nobody really knows what they’re doing, why they’re doing it, or whether they’re even solving the right problem in the first place.

It reminds me of that old joke about lumberjacks cutting down a forest. Eventually, the team leader climbs the tallest tree, looks around, and screams:

“Guys! We’re cutting down the wrong forest!”

And the workers below shout back:

“Who cares? We’re making great progress!”

And honestly, no LLM will save us here. Especially if we never even ask the right questions.

Others Lie About Knowing How To Do Something

This is basically an extension of the previous problem.

A less experienced developer picks up a task and confidently says:
“Yeah, I know how to do this.”

Unfortunately, what they often mean is:
“I hope I’ll somehow figure it out.” 😅

In reality, they may not know how to solve the problem, which tools to use, what architecture makes sense… or even what prompt to write to get useful help from a coding agent.

But they’re afraid to ask.

Because how will they look in front of the team? What will the manager think? The tech lead?

Best-case scenario: they eventually ask questions… just way too late.

Worst-case scenario: they never ask at all and deliver something completely wrong. Which often isn’t even caught properly because…

Someone Else Is Lying About Having Time

Now we’re entering senior and leadership territory 😅

The motivations differ. Some people build their self-worth around being “the reliable one.” Others are terrified of losing status, influence, or even their job.

So they keep taking on more:

  • the hardest tasks,
  • endless meetings,
  • refinements,
  • estimations,
  • discussions with support,
  • discussions with business,
  • code reviews,
  • architecture decisions,
  • documentation.

And let’s be honest. Something eventually has to break.

Human beings are not made of steel. Nobody can operate at 100% forever.

So what happens?
People start half-listening during calls. Code reviews become rushed. Documentation quietly dies in a corner somewhere.

But they still refuse to admit — either to others or to themselves — that they’re simply overloaded.

Some Developers Lie About How Long Things Will Take

I see this constantly with more advanced juniors and mids.

They throw around hilariously optimistic estimates with absolute confidence.

And sure — if they could work uninterrupted for eight straight hours, the application contained no legacy code, edge cases didn’t exist, and other humans never interacted with the system… then maybe the estimate would actually be correct 😄

Then sprint review arrives, and suddenly everybody is shocked that the team didn’t deliver everything.

But this is not the only problem with estimations.

I once worked with an especially funny senior developer.

He treated estimations like sacred truth. He would aggressively defend his numbers during planning sessions, insisting that this specific task was definitely extremely complicated. The rest of the team usually suspected it wasn’t that bad, but eventually we’d surrender just to end the discussion.

And then — at least three separate times — he personally picked up the exact task he had massively overestimated… and finished it in about an hour.

Which basically meant the estimation discussion itself took longer than implementing the feature 😅

Did this experience change his behavior?

Absolutely not.

And Some Lie About Knowing Everything Best

These are probably the most dangerous ones.

I avoid people who say things like:

“Use THIS technology and ONLY this technology because everything else is garbage.”

Insert your favorite holy war here:
Angular vs React, Java vs Python, Rust vs literally anything else 😄

I never write like that myself. Even when I publish something titled “I Love Tailwind. Sorry Not Sorry”, I still talk about its downsides and explain where it absolutely doesn’t make sense.

If one day I start claiming some technology is objectively perfect for every possible situation, please leave a comment saying:

“Sylwia, go touch grass immediately.” 😅

Honestly, I’ve always wondered where this sense of absolute certainty comes from.

Because not all of these people are even paid influencers. Some genuinely seem emotionally attached to technological holy wars. Others maybe only know one stack deeply, so everything else automatically feels “bad.”

And while this behavior is very common among tech influencers, you absolutely see it inside companies too.

The problem is that this kind of certainty can seriously damage projects. People stop questioning decisions. Other developers become afraid to speak up. Stakeholders assume “the confident person” must be right simply because they sound convinced.

Best-case scenario: you end up with an annoying developer who knows more about frameworks than the actual business domain.

Worst-case scenario: you end up with a terrible stack choice and spaghetti architecture held together by ego.

People Are Just… People

Of course, I’m not innocent either.

I’ve used many of these lies myself in the past. Maybe I’m older now. Maybe slightly wiser. Or maybe I just recognize these patterns more easily.

But I’m definitely still lying somewhere too. Maybe not to others — maybe to myself.

Because a lot of our problems in software development aren’t really technical problems at all. They’re deeply human problems:

  • ego,
  • insecurity,
  • fear of looking stupid,
  • fear of admitting mistakes,
  • fear of saying “I don’t know.”

And honestly, I don’t have some magical solution for this. We’re not going to require three years of therapy from every developer before allowing them into a sprint planning meeting 😄

But I have noticed one thing.

Very often, admitting you don’t know something, openly discussing uncertainty during planning, or simply saying:

“Sorry, I still don’t understand this. Could you explain it one more time?”

doesn’t make people see you as a worse developer.

Quite often, the opposite happens.

Suddenly communication inside the team improves. Other people start asking questions too. Conversations become more honest. Problems get discovered earlier.

Seriously. Try it at least once. You might be surprised 🙂

So… what kinds of developer lies do you see most often in your team?

Spring Boot Dockerfile Best Practices: Smaller, Faster, Safer Images

Spring Boot Dockerfile

Published 2026-05-21 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).

We’ve all been there – stuck with a bloated Spring Boot Dockerfile that’s slowing down our development cycle and increasing the risk of security vulnerabilities. A well-crafted spring boot dockerfile is essential for ensuring our applications are efficient, scalable, and secure. In our production environment, we’ve seen firsthand the impact of poorly optimized Docker images, with deployment times increasing by up to 50% due to unnecessary layers and dependencies.

  • Introduction to Spring Boot Dockerfile Best Practices
  • Understanding Docker Layers
  • Multi-Stage Build for Smaller Images
  • Optimizing Dependencies for Faster Builds
  • Security Considerations for Spring Boot Docker Images
  • Common Mistakes to Avoid
  • Frequently Asked Questions
  • Conclusion and Next Steps

Introduction to Spring Boot Dockerfile Best Practices

When building a Spring Boot application, it’s essential to follow best practices for creating efficient and secure Docker images. One of the key concepts to understand is the use of docker layers, which allows us to break down our image into smaller, reusable components. By doing so, we can reduce the overall size of our image and improve build times. For example, we can use the following Dockerfile to create a basic Spring Boot image:

FROM openjdk:21-jdk-alpine
ARG JAR_FILE=target/myapp.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

This Dockerfile uses the openjdk:21-jdk-alpine base image and copies our Spring Boot application jar file into the container.

Understanding Docker Layers

Docker layers are a fundamental concept in Docker, allowing us to build images in a modular and efficient way. Each layer represents a set of changes to the previous layer, and by using docker layers, we can avoid duplicating effort and reduce the overall size of our image. For example, if we have a Dockerfile that installs dependencies, copies our application code, and sets environment variables, each of these steps will create a new layer. We can use the docker history command to view the layers in our image:

docker history -H myapp

This will show us the layers in our image, along with the size of each layer and the command that created it.

Multi-Stage Build for Smaller Images

One of the most effective ways to reduce the size of our Docker image is to use a multi-stage build. This involves creating a separate stage for building our application, and then copying the resulting artifact into a smaller runtime stage. For example:

# Stage 1: Build
FROM maven:3.8.6-jdk-21 as build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src src
RUN mvn package

# Stage 2: Runtime
FROM openjdk:21-jdk-alpine
ARG JAR_FILE=target/myapp.jar
COPY --from=build ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

This Dockerfile uses two stages: one for building our application using Maven, and another for creating the runtime image. By doing so, we can avoid including unnecessary build dependencies in our runtime image.

Optimizing Dependencies for Faster Builds

When building a Spring Boot application, it’s essential to optimize our dependencies to reduce build times. One way to do this is to use the spring-boot-starter dependencies, which include only the necessary dependencies for our application. For example:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:3.2.0'
}

This will include only the necessary dependencies for a basic Spring Boot web application. We can also use tools like Baeldung’s Dependency Analyzer to identify and remove unnecessary dependencies.

Security Considerations for Spring Boot Docker Images

When creating a Spring Boot Docker image, it’s essential to consider security best practices. One way to do this is to use a non-root user to run our application, which can help prevent privilege escalation attacks. For example:

RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring

This will create a new user and group called spring, and then switch to that user to run our application. We can also use tools like OWASP’s Docker Security Cheat Sheet to identify and mitigate security vulnerabilities.

Common Mistakes

Here are some common mistakes to avoid when creating a Spring Boot Dockerfile:

  • Using an unnecessary base image
  • Including unnecessary dependencies
  • Not using a non-root user to run the application
  • Not optimizing Docker layers
  • Not using a multi-stage build

Frequently Asked Questions

What is the best base image to use for a Spring Boot application?

The best base image to use for a Spring Boot application is openjdk:21-jdk-alpine, which includes the OpenJDK 21 runtime and the Alpine Linux distribution.

How can I optimize my Docker layers for better performance?

To optimize your Docker layers, use the docker history command to view the layers in your image, and then use the --squash flag to combine unnecessary layers.

What is the difference between a single-stage and multi-stage build?

A single-stage build involves creating a single stage for building and running our application, while a multi-stage build involves creating separate stages for building and running our application.

How can I ensure my Spring Boot Docker image is secure?

To ensure your Spring Boot Docker image is secure, use a non-root user to run your application, and follow security best practices like those outlined in the Spring Security documentation.

Conclusion and Next Steps

In conclusion, creating an efficient and secure Spring Boot Dockerfile requires careful consideration of several factors, including Docker layers, dependencies, and security best practices. By following the tips and best practices outlined in this article, we can create smaller, faster, and safer images that improve our development cycle and reduce the risk of security vulnerabilities. To learn more about Spring Boot and Docker, check out the official Spring Boot documentation and the Docker documentation.

Spring Boot Dockerfile in production

Further Reading

Written by **Shubham Bhati* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on LinkedIn, GitHub, or read more at shubh2-0.github.io.*

Building Virtual IoT Interfaces Before Buying Hardware

Lately, I’ve been obsessed with the idea of combining frontend development with IoT.

Things like smart farm dashboards, logistics trackers, or interfaces that control real world devices genuinely interest me.

But here’s the reality:
I don’t own any hardware yet,
No microcontrollers,no sensors,no breadboards.
Just my laptop, curiosity, and the willingness to learn.

After going through absolute hell learning JavaScript (seriously… the errors were too much 😅), I decided I didn’t want to wait until I could afford hardware before getting started.

So I’m trying something different.Instead of jumping straight into electronics, I’m starting with the frontend side of IoT using simulated data.

I’ll be building dashboards with React, Vite, and Tailwind CSS while simulating sensor updates in the browser.

Things like:
🌱 Smart farm dashboards
📦 Logistics tracking interfaces
📊 Real-time UI experiments

The goal?
Learn how the interface works first, make mistakes, break things, improve, and eventually connect real hardware later.

I know there’ll be lots of errors along the way 😭, but I want to document the process honestly as I learn.

If you’ve worked with IoT before, what’s one thing you wish you knew earlier?

And if you’re learning too, what kind of project would you build first?

Drop your thoughts in the comments let’s connect 👋

I built a smarter SMS client for Android in Flutter — no AI, just clean parsing

I got tired of Google Messages mixing my OTPs, bank alerts, and promo spam into one cluttered inbox. So I built my own.
Msgs is an open-source Android SMS client I’ve been developing in Flutter. No AI, no cloud — everything is parsed on-device using pattern matching and regex.

What’s wrong with Google Messages?

Google Messages is polished, but its inbox is a mess if you get a lot of transactional SMS. OTPs, bank debits, and promotional spam all land in the same place. You have to hunt for what matters. Their “categories” tab exists but feels like an afterthought — and it doesn’t give you any visual context for your transactions.

What Msgs does differently

🔑 OTP copying from notification
When an OTP arrives, Msgs detects the code and the service name instantly. You can copy it directly from the notification — no need to open the app or the message thread.

💳 Transactions shown separately
Bank credits and debits are extracted and displayed as visual summary cards — amount, account ending, and type (debit/credit) at a glance. Not buried in a thread with 200 other bank messages.

📢 Promotional messages are blocked
Promo SMS never reaches your main inbox. Messages filters them out automatically so your inbox only shows what matters.
No AI involved
Everything is done with on-device pattern matching. Fast, private, and it works offline. Your messages never leave your phone.
Material You design
Messages follows Material 3 with full dynamic color support — it picks up your wallpaper colors and adapts the entire UI. Light and dark mode included.

Tech behind it

Flutter with BLoC state management
Isar Database for fast local indexing and search
Pattern-based classifier — no ML model, no network calls
Clean feature-driven architecture

Current status
The app is still in active development. SMS reading, OTP detection, transaction cards, and chat all work. MMS is coming next.
⭐ If this sounds useful, check out the repo and drop a star:
👉 Github Repo