Retool Platform on Azure

Self-hosted Retool on AKS, all in Terraform, fed by public data with a citation for every source

What this is

A working Retool platform I built and run on Microsoft Azure, the way a firm would operate one: a production instance and a nonprod upgrade lane on Azure Kubernetes Service, private Postgres, secrets in Key Vault and a self-hosted Infisical, and a scheduled pipeline that loads only data that is legal to publish.

Live dashboard (public): demo.retool.maxmccann.us, with ingestion pods, company risk and sources. Retool itself: retool.maxmccann.us and nonprod.retool.maxmccann.us (sign-in required). The figures below are rendered from the same read-only published layer the Retool apps query.

Architecture

Platform architecture Visitors resolve maxmccann.us through Cloudflare. The write-up page is proxied by Cloudflare to GitHub Pages; retool.maxmccann.us is delegated to Azure DNS, so platform traffic reaches an Azure Application Gateway directly. AKS runs Retool prod and nonprod, a read-only dashboard, and one CronJob per public data feed, which egress through a NAT gateway to public publishers. Data lands in Azure Database for PostgreSQL; secrets flow from Key Vault through Infisical. Visitors public web Cloudflare maxmccann.us zone (Terraform) Proxy + TLSmaxmccann.us NS delegationretool.maxmccann.us DNS only, not proxied GitHub Pageswrite-up page Microsoft Azure · centralus · all resources in Terraform Azure DNSretool.maxmccann.us zone Application GatewayLet's Encrypt TLS (DNS-01) resolves to Virtual network (private subnets) AKS (Pod Security: restricted) Retool prodnamespace retool Retool nonprodupgrade lane Read-only dashboarddemo.retool.maxmccann.us Ingestion CronJobsone pod per public feed Analytics buildone transaction, daily Infisical (private)per-namespace identities AGIC ingress controller PostgreSQL (Retool)prod, nonprod, Infisical PostgreSQL (public data)ingest: raw, loaders only analytics: published, read-only Key Vaultroot of trust Container Registrypinned image tags NAT Gatewaysingle egress IP Public data publishers IRS, SEC, DOL, ... ingestion egress: honest User-Agent, robots.txt honoured, 401/403/429 never retried

Icons: Microsoft Azure Architecture Icons (used per Microsoft's icon terms); Kubernetes icon set (Apache-2.0/CC-BY-4.0). Postgres, PostgreSQL and the Slonik Logo are trademarks or registered trademarks of the PostgreSQL Community Association of Canada, and used with their permission. Cloudflare, Retool, Infisical, Let's Encrypt and GitHub are shown by name only.

Illinois WARN layoff notices

Employers must file a WARN notice before a mass layoff or plant closing. These are every notice in the Illinois DCEO monthly reports since 2020, loaded fresh by the pipeline.

Notices per month

Largest filers by employees affected

CompanyNoticesEmployees affectedLatest notice

By county

CountyNoticesEmployees affected

Sources, and why each is publishable

Every source carries its licence and what was filtered out before anything left the publisher. A licence I could not verify is labelled as such rather than assumed.

SourceBasisFilters applied at ingestLast load

verified licence read on the publisher's own page · owner decision no licence published; my judgement, recorded as such · unverified no licence found yet

Vendor findings

The platform is composed from Retool's official Terraform modules for Azure. Reviewing every plan before applying it caught three defects in them, each patched in a vendored copy, with the proof written down and a note to drop the patch when upstream fixes it:

  1. Key Vault access silently removed on the second apply. The vault's access policy was declared inline while Retool's own services module added policies separately, so the next apply would have stripped Retool's access to its encryption key and database password.
  2. The nonprod lane was never routed. The ingress controller was hard-wired to the prod namespace, assuming one App Gateway per deployment. Widening its watch list served nonprod from the same gateway instead of adding a second one.
  3. Every request returned 502, with nothing in the logs. The controller's ingress class and the class resource's controller name disagreed, and the controller overwrites one with the other at startup, so it claimed no ingress at all. Traced in the controller's source, proved on the live cluster, then patched.