<?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: Alvaro Lopez</title>
    <description>The latest articles on DEV Community by Alvaro Lopez (@lopz).</description>
    <link>https://dev.to/lopz</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%2F3063935%2F0fe6b3ff-e49e-49fa-bf3b-65b330aa2ec1.jpg</url>
      <title>DEV Community: Alvaro Lopez</title>
      <link>https://dev.to/lopz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lopz"/>
    <language>en</language>
    <item>
      <title>How I provision a real Postgres database in 200ms using .NET 10 and Docker (No K8s allowed)</title>
      <dc:creator>Alvaro Lopez</dc:creator>
      <pubDate>Wed, 22 Jul 2026 07:58:16 +0000</pubDate>
      <link>https://dev.to/lopz/how-i-provision-a-real-postgres-database-in-200ms-using-net-10-and-docker-no-k8s-allowed-375m</link>
      <guid>https://dev.to/lopz/how-i-provision-a-real-postgres-database-in-200ms-using-net-10-and-docker-no-k8s-allowed-375m</guid>
      <description>&lt;p&gt;If you are building integration tests for your backend, you know the drill: you need a database. You don't want a fake in-memory mock, you want the real deal. But booting a Postgres container locally or inside a GitHub Actions runner takes several seconds (sometimes minutes if the image isn't cached). &lt;/p&gt;

&lt;p&gt;I got so annoyed by my CI pipelines hanging that I decided to build &lt;a href="https://trashdb.dev" rel="noopener noreferrer"&gt;TrashDB&lt;/a&gt;: an API that gives you a remote, ephemeral database on demand. My hard requirement was speed. I wanted a developer to hit my API and get a fully functional database connection string in under 300 milliseconds. &lt;/p&gt;

&lt;p&gt;Here is a look under the hood at how I architected the backend to achieve that using .NET 10 and bare-metal Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Skipping Kubernetes Entirely
&lt;/h2&gt;

&lt;p&gt;The first temptation when building an infrastructure orchestration tool is to reach for Kubernetes. But K8s is built for high availability and self-healing, not for sub-second ephemeral container provisioning. The control plane overhead alone would kill my 200ms latency budget.&lt;/p&gt;

&lt;p&gt;Instead, I went back to basics: a bare-metal VPS running a raw Docker daemon. My .NET 10 API acts as the sole orchestrator. It talks directly to the Docker Engine API via the Unix socket (or Docker HTTP API). &lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Architecture of a 200ms Boot
&lt;/h2&gt;

&lt;p&gt;To get a container running in milliseconds, you have to ruthlessly cut corners on things you don't need for an ephemeral test environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Pre-pulled Images:&lt;/strong&gt; The biggest bottleneck in container startup is pulling layers. The host machine runs a CRON job that ensures the latest &lt;code&gt;postgres:alpine&lt;/code&gt;, &lt;code&gt;redis:alpine&lt;/code&gt;, and &lt;code&gt;chromadb&lt;/code&gt; images are always downloaded and cached.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No Volumes:&lt;/strong&gt; These are throwaway databases. They don't need persistence. By bypassing volume mounts entirely, the Docker daemon doesn't have to deal with filesystem mapping or disk I/O bottlenecks during boot. It creates the container entirely in memory space.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Host Networking Bypass:&lt;/strong&gt; Instead of setting up complex internal Docker networks or reverse proxies (like Traefik or Nginx) which add routing latency, the .NET API simply asks Docker to map the container's internal port to a random open port on the host machine. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. The .NET 10 Orchestrator
&lt;/h2&gt;

&lt;p&gt;The core logic is surprisingly simple. When an HTTP &lt;code&gt;POST /v1/containers&lt;/code&gt; request hits the API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Validate:&lt;/strong&gt; The API checks the API key and requested engine.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Port Allocation:&lt;/strong&gt; It finds a free port on the host.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Docker API Call:&lt;/strong&gt; It sends a raw request to the local Docker socket to &lt;code&gt;Create&lt;/code&gt; and &lt;code&gt;Start&lt;/code&gt; a container using the pre-cached alpine image.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;TTL Tagging:&lt;/strong&gt; It applies a label to the container with an expiration timestamp.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Response:&lt;/strong&gt; It immediately returns the host IP and the dynamically mapped port to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A background &lt;code&gt;BackgroundService&lt;/code&gt; (Worker Service) in .NET polls the Docker engine every few seconds, looking for containers whose TTL has expired, and ruthlessly sends a &lt;code&gt;SIGKILL&lt;/code&gt; to clean them up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep It Simple, Stupid (KISS)
&lt;/h2&gt;

&lt;p&gt;Building TrashDB reminded me that we often over-engineer our infrastructure. By stripping away persistence, meshes, and clustering, you can push raw Docker to do incredible things incredibly fast.&lt;/p&gt;

&lt;p&gt;If you are tired of local Testcontainers melting your CI/CD pipelines, I packaged this entire architecture into a free Alpha. You can grab an API key at &lt;a href="https://trashdb.dev" rel="noopener noreferrer"&gt;trashdb.dev&lt;/a&gt; and get your test databases running in 200ms. &lt;/p&gt;

&lt;p&gt;Let me know in the comments: what is the weirdest hack you've used to speed up your integration tests?&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why mocking your Vector DB in CI is a terrible idea (and a 200ms fix)</title>
      <dc:creator>Alvaro Lopez</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:39:15 +0000</pubDate>
      <link>https://dev.to/lopz/why-mocking-your-vector-db-in-ci-is-a-terrible-idea-and-a-200ms-fix-4nmc</link>
      <guid>https://dev.to/lopz/why-mocking-your-vector-db-in-ci-is-a-terrible-idea-and-a-200ms-fix-4nmc</guid>
      <description>&lt;p&gt;Over the last year, building a RAG application has become incredibly easy. But if you’ve tried to move that prototype from a Jupyter Notebook to a production-grade CI/CD pipeline, you’ve probably hit a brick wall.&lt;/p&gt;

&lt;p&gt;Testing vector search in a GitHub Actions runner without melting the server is a logistical nightmare. When setting up &lt;code&gt;pytest&lt;/code&gt; for an app that relies on ChromaDB or Qdrant, engineering teams usually fall into one of two traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 1: The Mocking Illusion
&lt;/h3&gt;

&lt;p&gt;You decide to mock the database client using &lt;code&gt;unittest.mock&lt;/code&gt;. Your test asserts that &lt;code&gt;collection.query()&lt;/code&gt; was called with the right parameters. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt; You aren't actually testing anything useful. The entire point of a RAG pipeline is semantic search, the cosine similarity thresholds, embedding dimensions, and chunk retrieval. If you mock the vector database, you are flying blind. You’ll deploy to production only to realize your distance metrics were completely wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 2: The DinD Heavyweight
&lt;/h3&gt;

&lt;p&gt;You decide to do it right and use Testcontainers to spin up a real ChromaDB instance in your CI workflow. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt; Running Docker-in-Docker (DinD) in a GitHub runner (which gives you 7GB of RAM) is painfully slow. Pulling gigabytes of vector database images and booting the daemon consumes massive amounts of memory. Your CI pipeline goes from 45 seconds to 10 minutes, or simply crashes with an &lt;code&gt;OOMKilled&lt;/code&gt; error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating Compute from State
&lt;/h3&gt;

&lt;p&gt;The problem isn’t the database; it’s where we are running it. We are forcing our CI compute runners to also act as infrastructure provisioners. &lt;/p&gt;

&lt;p&gt;The ideal architecture for testing is separating the two. Your CI runner should do nothing but execute your test code. The database should live on a remote, isolated server, boot instantly, and destroy itself the moment the test finishes.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;TrashDB&lt;/strong&gt;. It’s an orchestration API (built in .NET 10 running on bare-metal) designed to do one thing: spin up databases in 200 milliseconds and kill them.&lt;/p&gt;

&lt;p&gt;I recently shipped a native Python SDK so you can integrate it directly into your &lt;code&gt;pytest&lt;/code&gt; fixtures without writing complex bash teardown scripts.&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;pytest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;trashdb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrashDBClient&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrashDBClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Spins up an ephemeral ChromaDB container in ~200ms
&lt;/span&gt;    &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chromadb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;

    &lt;span class="c1"&gt;# Cleans up the remote server automatically
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rag_retrieval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;chroma_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Your actual semantic search tests go here...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By externalizing the database, your runner only executes Python code. No heavy image pulls, no OOM crashes, and you can run multiple jobs in parallel without data collisions.&lt;/p&gt;

&lt;p&gt;I’m building TrashDB in public and it's currently in a free Alpha. If you are struggling with testing your AI apps in CI, grab an API key at &lt;a href="https://trashdb.dev" rel="noopener noreferrer"&gt;trashdb.dev&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Are you currently team mock, team Testcontainers, or something else entirely? Let me know below.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>devops</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
