A real ecommerce store turned into a modular, open-source Customer Data Platform. Every piece is swappable, every data point flows end-to-end.
π Store: https://dtorreshaus.com π Stack: GTM SS β RudderStack β PostgreSQL β dbt β HubSpot
βββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β Frontend β β GTM Server Side β β RudderStack β
β React + Vite ββββββΆβ sst.dtorreshaus ββββββΆβ Data Plane β
β Wompi Checkoutβ β (Docker / EC2) β β (self-hosted) β
βββββββββββββββββββ βββββββββββββββββββββ ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β HubSpot CRM βββββββ Reverse ETL βββββββ PostgreSQL β
β (Contacts) β β (Python Script) β β Warehouse β
βββββββββββββββββββ βββββββββββββββββββββ ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β dbt Models β
β Star Schema β
β (ELT Layer) β
βββββββββββββββββββ
Each block in the CDP uses an independent tool. Want to swap RudderStack for Segment? Just reconnect. Want to replace HubSpot with Salesforce? Just change the Reverse ETL script. Nothing is coupled; everything is composable.
| # | Layer | Tool | What it does |
|---|---|---|---|
| 1 | Collection | GTM Server Side | Captures frontend events (page views, add to cart, purchase) with first-party data |
| 2 | Ingestion | RudderStack (self-hosted) | Receives the event stream and loads it into the warehouse |
| 3 | Storage | PostgreSQL | Local data warehouse where raw events land |
| 4 | Transformation | dbt | Redacts PII, anonymizes IPs, classifies devices, and builds a star schema |
| 5 | Activation | Reverse ETL (Python) | Pushes transformed users to the CRM (HubSpot) as contacts |
ecommerce_elt/models/
βββ sources.yml # Raw source definitions
βββ stg_page_view_clean.sql # Staging: PII redaction + IP anonymization
βββ stg_hubspot_contacts.sql # Staging: HubSpot contacts normalization
βββ fct_customer_journey.sql # Fact table: Web + CRM JOIN β Star Schema
-- PII redaction: censors emails exposed in URLs
REGEXP_REPLACE(page_location, 'email=[^&]+', 'email=[REDACTED]', 'g')
-- IP anonymization: strips the last octet
REGEXP_REPLACE(context_ip, '\.\d+$', '.0')
-- Device classification from User-Agent
CASE
WHEN context_user_agent ILIKE '%Mobile%' THEN 'Mobile'
WHEN context_user_agent ILIKE '%iPad%' THEN 'Tablet'
ELSE 'Desktop'
END AS device_categoryJoins web traffic with CRM data to classify every user:
CASE
WHEN c.hubspot_id IS NOT NULL THEN 'Lead'
ELSE 'Visitor'
END AS user_statusResult: a dashboard-ready table that answers "Who visits my store and who is already a lead?"
# Spin up the entire ingestion stack
docker compose -f infra/rudderstack/rudder-docker.yml up -dIncludes: RudderStack Server, Transformer, PostgreSQL (jobs), MinIO (staging), and StatsD Exporter.
# Deploy the GTM Server Side container with SSL
./setup-gtm-server.shRuns as a Docker container behind Nginx with Certbot SSL on sst.dtorreshaus.com.
on:
push:
branches: [ main ]Automated pipeline: build frontend β deploy to EC2 β setup GTM Server β copy assets.
The real store that generates all the pipeline events.
| Component | Details |
|---|---|
| Frontend | React 18 + Vite 5 |
| Backend | Express + Prisma (PostgreSQL) |
| Payments | Wompi β Nequi, Cards, PSE (Colombia) |
| Products | 136 products across 10 categories |
| Hosting | AWS EC2 (Ubuntu) + Nginx + PM2 |
| SSL | Certbot (Let's Encrypt) |
git clone https://github.com/atikakernel/dtorreshaus.git
cd dtorreshaus
npm install
npm run dev # Frontend at http://localhost:5173# RudderStack + PostgreSQL warehouse
docker compose -f infra/rudderstack/rudder-docker.yml up -d
# Simulate browsing events
python simulador.pycd ecommerce_elt
dbt run # Run staging + fact table models
dbt test # Validate data integrity# Reverse ETL: push clean users to HubSpot
python reverse_etl.pydtorreshaus/
βββ src/ # Frontend β React + Vite
β βββ components/
β β βββ Checkout.jsx # Checkout with Wompi
β βββ services/api.js # API client
β βββ App.jsx # Main component
β
βββ backend/ # API β Express + Prisma
β βββ server.js
β βββ routes/
β βββ prisma/ # DB schema
β
βββ ecommerce_elt/ # β dbt Project (ELT Layer)
β βββ models/
β βββ stg_page_view_clean.sql
β βββ stg_hubspot_contacts.sql
β βββ fct_customer_journey.sql
β
βββ infra/
β βββ rudderstack/
β βββ rudder-docker.yml # Docker Compose β Data Plane
β βββ workspaceConfig.json
β
βββ simulador.py # Event generator β RudderStack
βββ reverse_etl.py # PostgreSQL β HubSpot (Activation)
βββ verify_fct.py # Star Schema verification
βββ setup-gtm-server.sh # GTM Server Side deploy on EC2
βββ run_ec2_deploy.sh # Backend deploy on EC2
β
βββ .github/workflows/
β βββ deploy.yml # CI/CD: GitHub Actions β EC2
β
βββ docs/
βββ DEPLOYMENT.md # Full deployment guide
| Layer | Technology |
|---|---|
| Collection | Google Tag Manager Server Side |
| Ingestion | RudderStack (self-hosted, Docker) |
| Warehouse | PostgreSQL 15 |
| Transformation | dbt Core |
| Activation | Python (Reverse ETL β HubSpot API) |
| Frontend | React 18, Vite 5 |
| Backend | Node.js 18, Express, Prisma |
| Payments | Wompi (Nequi, Cards, PSE) |
| Infra | AWS EC2, Nginx, PM2, Certbot |
| CI/CD | GitHub Actions |
- β Composable CDP β Every piece of the stack is independently replaceable
- β First-party data collection β GTM Server Side, no third-party cookie dependency
- β Data governance β PII redaction and IP anonymization at the transformation layer
- β Star Schema β Fact table joining web analytics with CRM data
- β Reverse ETL β Transformed data activated back into the CRM
- β Self-hosted stack β RudderStack and PostgreSQL run locally, zero vendor lock-in
- β Infrastructure as Code β Docker Compose + setup scripts + automated CI/CD
- β Real ecommerce β Not a demo β a live store with real payments in Colombia π¨π΄
Diego Castellanos β Data Engineer
- π GitHub: atikakernel
- π Store: dtorreshaus.com
Β© 2025 dtorreshaus β Composable CDP for Ecommerce π¨π΄