<?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: Alex G</title>
    <description>The latest articles on DEV Community by Alex G (@alex_g_aeeb05ba69eee8a4fd).</description>
    <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3720520%2F7ad3e51a-7ba6-4244-8e53-1487a6ead4ff.jpg</url>
      <title>DEV Community: Alex G</title>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alex_g_aeeb05ba69eee8a4fd"/>
    <language>en</language>
    <item>
      <title>Custom Polygons vs. Uber's H3: Building a High-Performance Geofencing Backend in Go</title>
      <dc:creator>Alex G</dc:creator>
      <pubDate>Tue, 31 Mar 2026 18:46:59 +0000</pubDate>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd/custom-polygons-vs-ubers-h3-building-a-high-performance-geofencing-backend-in-go-3e7f</link>
      <guid>https://dev.to/alex_g_aeeb05ba69eee8a4fd/custom-polygons-vs-ubers-h3-building-a-high-performance-geofencing-backend-in-go-3e7f</guid>
      <description>&lt;p&gt;When building logistics and telemetry platforms, processing thousands of GPS pings per second is just a regular Tuesday. The core challenge isn't just receiving the data; it's figuring out &lt;em&gt;exactly&lt;/em&gt; where that data is in relation to your business logic. Is the truck inside the warehouse? Did it cross a restricted zone?&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;If you are building a geofencing architecture, you will inevitably face the dilemma: &lt;strong&gt;Should you use exact Custom Polygons (PostGIS) or spatial indexing grids like Uber's H3?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spoiler alert: For a truly scalable and user-friendly system, you need both. Here is how we handle this hybrid architecture using Go and PostGIS.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Reality: Users Don't Think in Hexagons
&lt;/h2&gt;

&lt;p&gt;From a purely mathematical standpoint, Uber’s H3 is a masterpiece. But from a UX perspective, telling a warehouse manager to draw their loading dock using only rigid hexagons is a terrible idea. Real-world facilities (like a CEDIS or a distribution center) are irregular. &lt;/p&gt;

&lt;p&gt;Users need to draw custom polygons on a map. In our Go backend, we typically store these directly in PostgreSQL using PostGIS.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Standard Approach: PostGIS &lt;code&gt;ST_Contains&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When a GPS ping hits our Go server, the most straightforward approach is to ask PostGIS if the point is inside any stored polygon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// A simplified example of checking a GPS ping against PostGIS polygons&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"database/sql"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"github.com/lib/pq"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;checkGeofence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;`
        SELECT fence_id, name 
        FROM geofences 
        WHERE ST_Contains(geom, ST_SetSRID(ST_MakePoint($1, $2), 4326));
    `&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fenceID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;fenceID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNoRows&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// Not in any geofence&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The Catch:&lt;/strong&gt; &lt;code&gt;ST_Contains&lt;/code&gt; uses spatial math (like ray casting) to determine if a point is inside a polygon. Even with GiST indexes, running complex spatial intersections for 10,000 pings per second will melt your database CPU and spike your cloud bill. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Scaling Secret: Enter Uber's H3
&lt;/h2&gt;

&lt;p&gt;When we need to eliminate database latency and scale massively, we bring H3 into the backend. &lt;/p&gt;

&lt;p&gt;H3 divides the world into a grid of hexagons. Instead of doing complex math, H3 converts a &lt;code&gt;(latitude, longitude)&lt;/code&gt; pair into a simple string or integer (e.g., &lt;code&gt;8928308280fffff&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Why is this a game-changer? &lt;strong&gt;Because checking if a truck is in a zone goes from an expensive spatial calculation to an $O(1)$ Hash Map lookup.&lt;/strong&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Hybrid Architecture: "Polyfill"
&lt;/h3&gt;

&lt;p&gt;We don't force users to draw hexagons. We let them draw their precise custom polygons. Then, in the Go backend, we run an asynchronous worker that takes that polygon and "fills" it with H3 hexagons (a process called Polyfill).&lt;/p&gt;

&lt;p&gt;We store these hexagon IDs in a fast key-value store (like Redis) or a simple indexed database table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/uber/h3-go/v4"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;indexGeofenceWithH3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polygon&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Create a GeoLoop from the user's custom polygon&lt;/span&gt;
    &lt;span class="n"&gt;geoLoop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGeoLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;geoPolygon&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGeoPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geoLoop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Fill the polygon with H3 hexagons at the desired resolution&lt;/span&gt;
    &lt;span class="n"&gt;hexagons&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolygonToCells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geoPolygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hexagons&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleIncomingPing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 1. Convert incoming ping to H3 index instantly&lt;/span&gt;
    &lt;span class="n"&gt;latLng&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewLatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="c"&gt;// Roughly city-block size&lt;/span&gt;
    &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LatLngToCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// 2. O(1) Lookup: Check if this cell ID exists in our Redis cache&lt;/span&gt;
    &lt;span class="c"&gt;// cache.Exists(cell.String())&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ping mapped to Hexagon: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&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;
  
  
  The Best of Both Worlds
&lt;/h2&gt;

&lt;p&gt;By using Go, PostGIS, and H3 together, you build a logistics engine that is both precise and brutally fast:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;UX / Precision:&lt;/strong&gt; Users draw exact custom polygons.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Storage:&lt;/strong&gt; PostGIS acts as the source of truth for the exact geometries.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Real-Time Processing:&lt;/strong&gt; Go converts incoming pings to H3 indexes and checks them against a cached set of Polyfilled hexagons in microseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no silver bullet in software engineering, but for real-time logistics telemetry, combining PostGIS precision with H3 speed is as close as it gets.&lt;/p&gt;

</description>
      <category>go</category>
      <category>postgis</category>
      <category>h3</category>
      <category>backend</category>
    </item>
    <item>
      <title>Stop Configuring Nginx: The Easiest Way to Deploy Go &amp; React with HTTPS</title>
      <dc:creator>Alex G</dc:creator>
      <pubDate>Wed, 11 Feb 2026 01:19:16 +0000</pubDate>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd/stop-configuring-nginx-the-easiest-way-to-deploy-go-react-with-https-51ma</link>
      <guid>https://dev.to/alex_g_aeeb05ba69eee8a4fd/stop-configuring-nginx-the-easiest-way-to-deploy-go-react-with-https-51ma</guid>
      <description>&lt;p&gt;&lt;strong&gt;The "It Works on My Machine" Trap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have all been there. You spend weeks building a robust application. Your Go backend is blazing fast, your React frontend is snappy, and everything runs perfectly on &lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But then comes the deployment phase.&lt;/p&gt;

&lt;p&gt;Suddenly, you are dealing with VPS configuration, SSL certificates, Nginx config files that look like hieroglyphics, and the dreaded CORS errors.&lt;/p&gt;

&lt;p&gt;I recently built &lt;strong&gt;Geo Engine&lt;/strong&gt;, a geospatial backend service using Go and PostGIS. I wanted to deploy it to a DigitalOcean Droplet with a custom domain and HTTPS, but I didn't want to spend hours configuring Certbot or managing complex Nginx directives.&lt;/p&gt;

&lt;p&gt;Here is how I solved it using &lt;strong&gt;Docker Compose&lt;/strong&gt; and &lt;strong&gt;Caddy&lt;/strong&gt; (the web server that saves your sanity).&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Architecture 🏗️
&lt;/h3&gt;

&lt;p&gt;My goal was to have a professional production environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; A React Dashboard (Vite) on &lt;code&gt;app.geoengine.dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; A Go API (Chi Router + PostGIS) on &lt;code&gt;api.geoengine.dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Automatic HTTPS for both subdomains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; Everything containerized with Docker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of exposing ports &lt;code&gt;8080&lt;/code&gt; and &lt;code&gt;5173&lt;/code&gt; to the wild, I used &lt;strong&gt;Caddy&lt;/strong&gt; as the entry point. Caddy acts as a reverse proxy, handling SSL certificate generation and renewal automatically.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The "Magic" Caddyfile ✨
&lt;/h3&gt;

&lt;p&gt;If you have ever struggled with an &lt;code&gt;nginx.conf&lt;/code&gt; file, you are going to love this. This is literally all the configuration I needed to get HTTPS working for two subdomains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The Dashboard (Frontend)
app.geoengine.dev {
    reverse_proxy dashboard:80
}

# The API (Backend)
api.geoengine.dev {
    reverse_proxy api:8080
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
That’s it. Caddy detects the domain, talks to Let's Encrypt, gets the certificates, and routes the traffic. No cron jobs, no manual renewals.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Docker Setup 🐳
&lt;/h3&gt;

&lt;p&gt;Here is the secret sauce in my &lt;code&gt;docker-compose.yml&lt;/code&gt;. Notice how the services don't expose ports to the host machine (except Caddy); they only talk inside the &lt;code&gt;geo-net&lt;/code&gt; network.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Caddy: The only service exposed to the world&lt;/span&gt;
  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2-alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_data:/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;geo-net&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dashboard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;

  &lt;span class="c1"&gt;# Backend API&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt; &lt;span class="c1"&gt;# Only visible to Caddy, not the internet&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALLOWED_ORIGINS=https://app.geoengine.dev&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;geo-net&lt;/span&gt;

  &lt;span class="c1"&gt;# Database&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="c1"&gt;# ... config ...&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;geo-net&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;geo-net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  The Challenges (Where I Got Stuck) 🚧
&lt;/h3&gt;

&lt;p&gt;It wasn't all smooth sailing. Here are two "gotchas" that cost me a few hours of debugging, so you don't have to suffer:&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  1. The "Orphan" Migration Container
&lt;/h4&gt;

&lt;p&gt;I use a separate container to run database migrations (&lt;code&gt;golang-migrate&lt;/code&gt;). It kept crashing with a connection error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; I realized that even utility containers need to be on the same Docker network! I had forgotten to add &lt;code&gt;networks: - geo-net&lt;/code&gt; to my migration service, so it couldn't "see" the database.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  2. The CORS Villain 💀
&lt;/h4&gt;

&lt;p&gt;On localhost, allowing &lt;code&gt;*&lt;/code&gt; (wildcard) for CORS usually works. But once I moved to production with HTTPS, my frontend requests started failing.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Browsers are strict about credentials (cookies/headers) in secure environments. I had to stop being lazy and specify the exact origin in my Go code using the &lt;code&gt;rs/cors&lt;/code&gt; library.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
&lt;strong&gt;In Go:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Don't do this in production:&lt;/span&gt;
&lt;span class="c"&gt;// AllowedOrigins: []string{"*"} ❌&lt;/span&gt;

&lt;span class="c"&gt;// Do this instead:&lt;/span&gt;
&lt;span class="n"&gt;AllowedOrigins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"https://app.geoengine.dev"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;✅&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By matching the exact origin of my frontend, the browser (and the security protocols) were happy.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;After pushing the changes, I ran &lt;code&gt;docker compose up -d&lt;/code&gt;. In about 30 seconds, Caddy had secured my site.&lt;/p&gt;

&lt;p&gt;You can check out the live demo here: &lt;strong&gt;&lt;a href="https://app.geoengine.dev" rel="noopener noreferrer"&gt;https://app.geoengine.dev&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Or explore the code on GitHub: &lt;strong&gt;&lt;a href="https://github.com/AlexG695/Geo-Engine-Core" rel="noopener noreferrer"&gt;Link GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are deploying a side project, give Caddy a try. It feels like cheating, but in the best way possible.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Flutter Security: Why `isMockLocation` Is Dead in 2026 (And How to Fix It)</title>
      <dc:creator>Alex G</dc:creator>
      <pubDate>Wed, 04 Feb 2026 19:09:02 +0000</pubDate>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd/flutter-security-why-ismocklocation-is-dead-in-2026-and-how-to-fix-it-2odn</link>
      <guid>https://dev.to/alex_g_aeeb05ba69eee8a4fd/flutter-security-why-ismocklocation-is-dead-in-2026-and-how-to-fix-it-2odn</guid>
      <description>&lt;p&gt;If you are building a logistics, ride-sharing, or field-attendance app in Flutter, you have likely written this line of code before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Block user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five years ago, this was enough. &lt;strong&gt;In 2026, this is security theater.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The truth is, if your business relies on accurate location data (delivery tracking, clock-in systems), relying solely on &lt;code&gt;isMockLocation&lt;/code&gt; is costing you money.&lt;/p&gt;

&lt;p&gt;Here is why the old methods fail and how to implement &lt;strong&gt;Device Integrity&lt;/strong&gt; validation for a bulletproof solution.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem: The "Cat and Mouse" Game
&lt;/h2&gt;

&lt;p&gt;Standard location packages in Flutter usually check the &lt;code&gt;android.location.Location.isFromMockProvider()&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;However, modern spoofing tools have evolved. Users today utilize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rooted Devices with Magisk:&lt;/strong&gt; Modules that hide the root status from the OS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smali Patcher:&lt;/strong&gt; Tools that patch the Android framework to always return &lt;code&gt;false&lt;/code&gt; for mock location checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Emulators:&lt;/strong&gt; BlueStacks or custom Android builds that simulate physical GPS hardware signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these scenarios, your app asks the OS: &lt;em&gt;"Is this fake?"&lt;/em&gt;&lt;br&gt;
The compromised OS replies: &lt;em&gt;"No, trust me."&lt;/em&gt;&lt;br&gt;
And your backend accepts the fraudulent data.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Don't Trust the Device
&lt;/h2&gt;

&lt;p&gt;To truly secure your geolocation flow, you cannot trust the device to validate itself. You need a higher authority.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;Google Play Integrity API&lt;/strong&gt; (formerly SafetyNet).&lt;/p&gt;

&lt;p&gt;Instead of a simple boolean check, the flow changes to a &lt;strong&gt;Challenge-Response&lt;/strong&gt; architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Challenge:&lt;/strong&gt; Your backend sends a unique random string (nonce).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Attestation:&lt;/strong&gt; Your Flutter app sends this nonce to Google's servers via the Play Integrity API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Token:&lt;/strong&gt; Google analyzes the device binary, the bootloader, and the GPS hardware, returning an encrypted token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Verdict:&lt;/strong&gt; Your app sends this token to your backend. Your backend decrypts it with Google's public key to verify:&lt;/li&gt;
&lt;li&gt;Is the app binary unmodified?&lt;/li&gt;
&lt;li&gt;Is the device physical and not an emulator?&lt;/li&gt;
&lt;li&gt;Is the location likely genuine?&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  The Implementation Pain
&lt;/h2&gt;

&lt;p&gt;While the theory is sound, implementing this in Flutter can be a headache. You need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up Google Cloud projects.&lt;/li&gt;
&lt;li&gt;Handle platform channels for Android (Play Integrity) and iOS (DeviceCheck).&lt;/li&gt;
&lt;li&gt;Manage token decryption on your backend (Node, Go, Python).&lt;/li&gt;
&lt;li&gt;Handle timeouts and retry logic.

## The "Easy Button": Introducing Geo-Engine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After struggling with this implementation for a logistics project, I decided to wrap this entire complexity into a reusable SDK.&lt;/p&gt;

&lt;p&gt;I created &lt;strong&gt;Geo-Engine&lt;/strong&gt;, a specialized Flutter package that handles the integrity handshake natively.&lt;/p&gt;

&lt;p&gt;It doesn't just check for mock providers; it validates the &lt;strong&gt;integrity of the device&lt;/strong&gt; itself before allowing location tracking to start.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How it looks in code
&lt;/h3&gt;

&lt;p&gt;Geo-Engine is designed to be non-invasive. You don't need to rewrite your entire location logic. It works alongside your existing location provider (like &lt;code&gt;geolocator&lt;/code&gt; or &lt;code&gt;location&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;It acts as a secure "middleware" that buffers data offline, generates the Integrity Token automatically, and flushes data to the server only when the device is verified.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Here is a real-world implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:geo_engine/geo_engine.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;WidgetsFlutterBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Initialize the local buffer (Hive)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;GeoEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... inside your service or controller ...&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocationService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="n"&gt;GeoEngine&lt;/span&gt; &lt;span class="n"&gt;_engine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. Configure the Engine with your credentials&lt;/span&gt;
    &lt;span class="n"&gt;_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeoEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;apiKey:&lt;/span&gt; &lt;span class="s"&gt;"YOUR_PROJECT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Crucial: This enables Google Play Integrity on Android&lt;/span&gt;
      &lt;span class="nl"&gt;androidCloudProjectNumber:&lt;/span&gt; &lt;span class="s"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nl"&gt;debug:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. When you get a location update (e.g. from geolocator)&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;onLocationUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 4. Send it securely.&lt;/span&gt;
    &lt;span class="c1"&gt;// The SDK automatically handles:&lt;/span&gt;
    &lt;span class="c1"&gt;// - Offline buffering (if network is lost)&lt;/span&gt;
    &lt;span class="c1"&gt;// - Attaching the Integrity Token (X-Device-Integrity header)&lt;/span&gt;
    &lt;span class="c1"&gt;// - Batching updates to save battery&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;deviceId:&lt;/span&gt; &lt;span class="s"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nl"&gt;latitude:&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nl"&gt;longitude:&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&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;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If your app handles payments or sensitive operations based on location, relying on client-side flags like &lt;code&gt;isMockLocation&lt;/code&gt; is a vulnerability.&lt;/p&gt;

&lt;p&gt;Moving to &lt;strong&gt;Server-Side Integrity Validation&lt;/strong&gt; is the industry standard for 2026. Whether you build the integration manually or use a package like &lt;code&gt;geo_engine&lt;/code&gt;, upgrading your security layer is no longer optional—it's necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📦 Check out Geo-Engine on Pub.dev:&lt;/strong&gt; &lt;a href="https://pub.dev/packages/geo_engine_sdk" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy coding and stay secure!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>security</category>
      <category>dart</category>
    </item>
    <item>
      <title>Building a Serverless Geofencing Engine with Go &amp; PostGIS (to replace expensive APIs)</title>
      <dc:creator>Alex G</dc:creator>
      <pubDate>Thu, 29 Jan 2026 00:46:32 +0000</pubDate>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd/building-a-serverless-geofencing-engine-with-go-postgis-to-replace-expensive-apis-78i</link>
      <guid>https://dev.to/alex_g_aeeb05ba69eee8a4fd/building-a-serverless-geofencing-engine-with-go-postgis-to-replace-expensive-apis-78i</guid>
      <description>&lt;p&gt;I recently started working on a logistics side project that required real-time geofencing—specifically, detecting when assets enter or exit defined polygon zones.&lt;/p&gt;

&lt;p&gt;I looked at the market leaders (Radar, Google Maps Geofencing API, etc.), and while they are excellent, the pricing models usually charge per tracked user or per API call. For a bootstrapped project where I might have thousands of "pings" but zero revenue initially, paying for every spatial check wasn't viable.&lt;/p&gt;

&lt;p&gt;So, I decided to engineer my own solution.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Here is a breakdown of how I built a serverless, event-driven Geo-fencing Engine using &lt;strong&gt;Go&lt;/strong&gt;, &lt;strong&gt;PostGIS&lt;/strong&gt;, and &lt;strong&gt;Cloud Run&lt;/strong&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Requirements
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Real-time:&lt;/strong&gt; The latency between a location ping and a webhook event needed to be sub-second.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Scalable to Zero:&lt;/strong&gt; I didn't want to pay for a K8s cluster idling at 3 AM.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Stateless:&lt;/strong&gt; The system needed to handle concurrent streams without sticky sessions.

## The Architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose &lt;strong&gt;Google Cloud Platform (GCP)&lt;/strong&gt; for the infrastructure, managed via Terraform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu3n09oetnh47uya8mby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu3n09oetnh47uya8mby.png" alt="ARCHITECTURE DIAGRAM" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The Compute Layer: Go + Cloud Run
&lt;/h3&gt;

&lt;p&gt;I wrote the ingestion service in &lt;strong&gt;Go 1.22&lt;/strong&gt;. Go was the obvious choice for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency:&lt;/strong&gt; Handling thousands of incoming HTTP requests with lightweight goroutines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold Starts:&lt;/strong&gt; Since I'm using Cloud Run (serverless), the service scales down to zero when not in use. Go binaries start up incredibly fast compared to JVM or Node.js containers, minimizing the "cold start" latency penalty.

### 2. The Spatial Layer: PostGIS
This is where the heavy lifting happens. I'm using &lt;strong&gt;Cloud SQL (PostgreSQL)&lt;/strong&gt; with the &lt;strong&gt;PostGIS&lt;/strong&gt; extension.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of doing "Point-in-Polygon" math in the application layer (which is CPU intensive and complex to handle for complex polygons/multipolygons), I offload this to the database.&lt;/p&gt;

&lt;p&gt;The core logic effectively boils down to efficient spatial indexing using GiST indexes and queries like:&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;zone_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;geofences&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ST_Intersects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geofence_geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_SetSRID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_MakePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  3. The "Glue": The Client SDKs
&lt;/h3&gt;

&lt;p&gt;Building the backend was only half the battle. The friction usually lies in the mobile app integration—handling location permissions, battery-efficient tracking, and buffering offline requests.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
To solve this, I built (and open-sourced) client SDKs. For example, the Flutter SDK handles the ingestion stream and retries, acting as a clean interface to the engine.&lt;br&gt;
Trade-offs &amp;amp; Decisions&lt;br&gt;
&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Why not Redis (Geo)?&lt;/strong&gt; Redis has geospatial capabilities (GEOADD, GEORADIUS), but it is primarily optimized for "radius" (point + distance) queries. My use case required strict Polygon geofencing (complex shapes). While Redis 6.2+ added some shape support, PostGIS remains the gold standard for robust topological operations.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Why Serverless? The traffic pattern for logistics is spiky. It peaks during business hours and drops to near zero at night. Cloud Run allows me to pay strictly for the CPU time used during ingestion, rather than provisioning a fixed server.&lt;br&gt;
Open Source?&lt;br&gt;
&lt;br&gt;&lt;br&gt;
While the core backend engine runs internally for my project (to keep the infrastructure managed), I realized the Client SDKs are valuable on their own as a reference for structuring location ingestion.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
I’ve open-sourced the SDKs to share how the protocol works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go SDK:&lt;/strong&gt; &lt;a href="https://github.com/AlexG695/geo-engine-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter SDK:&lt;/strong&gt; &lt;a href="https://github.com/AlexG695/geo-engine-dart" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm currently optimizing the "State Drift" issue in Terraform and looking into moving the event bus to Pub/Sub for better decoupling.&lt;/p&gt;

&lt;p&gt;I’d love to hear feedback on the architecture—specifically if anyone has experience scaling PostGIS for high-write workloads!&lt;/p&gt;

</description>
      <category>go</category>
      <category>postgis</category>
      <category>systemdesign</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Arquitectura Serverless en GCP con Terraform: Cómo ahorré costos y sobreviví al "State Drift"</title>
      <dc:creator>Alex G</dc:creator>
      <pubDate>Thu, 22 Jan 2026 00:35:23 +0000</pubDate>
      <link>https://dev.to/alex_g_aeeb05ba69eee8a4fd/arquitectura-serverless-en-gcp-con-terraform-como-ahorre-costos-y-sobrevivi-al-state-drift-2b88</link>
      <guid>https://dev.to/alex_g_aeeb05ba69eee8a4fd/arquitectura-serverless-en-gcp-con-terraform-como-ahorre-costos-y-sobrevivi-al-state-drift-2b88</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  La historia de cómo conecté Cloud Run con Cloud SQL usando IP privada sin pagar el conector VPC, y cómo recuperé mi infraestructura cuando Terraform "olvidó" todo.
&lt;/h2&gt;
&lt;/blockquote&gt;



&lt;h3&gt;
  
  
  El sueño del MVP barato
&lt;/h3&gt;

&lt;p&gt;Cuando empecé a construir &lt;em&gt;Geo-Engine&lt;/em&gt;, mi SaaS de procesamiento geoespacial con Go, tenía una meta clara: quería una arquitectura profesional, pero no quería pagar cientos de dólares al mes mientras buscaba mis primeros usuarios.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
La elección obvia fue Google Cloud Platform (GCP):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloud Run: Para escalar a cero (pagar solo por uso).&lt;/li&gt;
&lt;li&gt;Cloud SQL (Postgres): Para tener una base de datos gestionada y sólida.&lt;/li&gt;
&lt;li&gt;Terraform: Para tener todo como código (IaC).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gkpcezqwm7zlcxzibl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gkpcezqwm7zlcxzibl9.png" alt=" " width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Sonaba perfecto en papel, pero la realidad me golpeó con dos problemas que casi descarrilan el proyecto: Terraform queriendo borrar mi producción y una factura potencial innecesaria por conectividad de red. Aquí te cuento cómo solucioné ambos.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problema 1: La Amnesia de Terraform (State Drift)
&lt;/h3&gt;

&lt;p&gt;Desarrollo desde mi laptop (Fedora) y a veces desde la nube (Project IDX). Un día, al intentar desplegar desde el entorno nuevo, Terraform me dio el susto de mi vida:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Terraform will perform the following actions:
  &lt;span class="c"&gt;# google_cloud_run_service.api will be created&lt;/span&gt;
  &lt;span class="c"&gt;# google_sql_database_instance.master will be created&lt;/span&gt;
...
Plan: 5 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Crear? ¡Pero si ya existían! Terraform quería duplicar todo (o borrar y recrear).&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Lo que aprendí: El archivo &lt;em&gt;terraform.tfvars&lt;/em&gt; (donde guardo mi project_id y variables sensibles) estaba en mi &lt;em&gt;.gitignore&lt;/em&gt;. Al cambiar de entorno, Terraform usó valores por defecto, apuntó a un "limbo" y decidió que no había nada creado.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  La Solución:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Recrear variables: Asegurarme de que el &lt;em&gt;project_id&lt;/em&gt; en el nuevo entorno fuera idéntico al real.&lt;/li&gt;
&lt;li&gt;Importar, no recrear: Usé el comando &lt;em&gt;import&lt;/em&gt; para decirle a Terraform: "Oye, este recurso ya existe, mételo en tu memoria".
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform import google_cloud_run_v2_service.default projects/MI_PROYECTO/locations/us-central1/services/geo-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esto sincronizó mi estado local con la realidad de la nube y el plan volvió a decir "No changes". Paz mental restaurada.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Problema 2: Conectar Cloud Run a SQL (La trampa de los $17 USD)
&lt;/h3&gt;

&lt;p&gt;Por seguridad, mi base de datos solo tiene IP Privada. Pero Cloud Run vive "fuera" de mi VPC. La documentación oficial recomienda usar un Serverless VPC Access Connector. El problema: Ese conector cuesta mínimo ~$17 USD/mes porque mantiene instancias VM encendidas 24/7. Para un MVP sin ingresos, eso es un "impuesto" doloroso.&lt;/p&gt;
&lt;h3&gt;
  
  
  La Solución: Direct VPC Egress.
&lt;/h3&gt;

&lt;p&gt;Descubrí que Cloud Run ahora soporta una conexión directa a la VPC sin intermediarios costosos. Solo necesitaba configurar correctamente el bloque de red en Terraform.&lt;/p&gt;

&lt;p&gt;Aquí está el fragmento de código que me ahorró esos $200 dólares al año:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_v2_service"&lt;/span&gt; &lt;span class="s2"&gt;"default"&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;"geo-api"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1"&lt;/span&gt;

  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ... configuración del contenedor ...&lt;/span&gt;

    &lt;span class="nx"&gt;vpc_access&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;network_interfaces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;network&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;subnetwork&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="c1"&gt;# ESTA ES LA CLAVE:&lt;/span&gt;
      &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PRIVATE_RANGES_ONLY"&lt;/span&gt;
    &lt;span class="p"&gt;}&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;Con &lt;strong&gt;PRIVATE_RANGES_ONLY&lt;/strong&gt;, Cloud Run enruta el tráfico hacia mi base de datos (IP 10.x.x.x) a través de la red interna de Google, y el resto del tráfico sale a internet normalmente. Costo extra: $0.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Construir &lt;em&gt;Geo-Engine&lt;/em&gt; me enseñó que la infraestructura como código es poderosa, pero requiere disciplina. Hoy tengo un stack corriendo Go, Postgres y Cloud Run que es:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Seguro: Todo en red privada.&lt;/li&gt;
&lt;li&gt;    Económico: Escala a cero y no paga conectores ociosos.&lt;/li&gt;
&lt;li&gt;    Reproducible: Gracias a Terraform (y a saber usar import).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si te interesa ver el resultado final, puedes probar la beta de mi herramienta aquí: &lt;a href="https://geoengine.dev" rel="noopener noreferrer"&gt;Geo-Engine&lt;/a&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🎁 Bonus: SDK de Node.js Oficial
&lt;/h2&gt;

&lt;p&gt;Mientras ajustaba la infraestructura, me di cuenta de que necesitaba una forma fácil de consumir mi propia API. Así que aproveché para empaquetar y publicar el SDK oficial.&lt;/p&gt;

&lt;p&gt;Usando &lt;code&gt;tsup&lt;/code&gt;, logré generar un paquete híbrido que soporta tanto &lt;strong&gt;ES Modules&lt;/strong&gt; (moderno) como &lt;strong&gt;CommonJS&lt;/strong&gt; (legacy) automáticamente.&lt;/p&gt;

&lt;p&gt;La estructura final quedó así de limpia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;geo-engine-node/
├── dist/
│   ├── index.js      (ES Modules - import)
│   ├── index.cjs     (CommonJS - require)
│   └── index.d.ts    (TypeScript Definitions)
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>gcp</category>
      <category>terraform</category>
      <category>serverless</category>
      <category>go</category>
    </item>
  </channel>
</rss>
