The Problem

Product teams need a single, self‑hosted stack that captures analytics, feature flags, error tracking and LLM observability without wiring together dozens of SaaS tools. Managing separate services creates latency, data silos and operational overhead.

What This Does

The repo is a portfolio of 16 independent projects that together implement PostHog’s platform. The two largest components are:

  • posthog/ – the core backend (≈4.5 k code files) built on Django/Python, exposing REST/GraphQL APIs, handling event ingestion, HogQL parsing (common/hogql_parser/), and background workers.
  • frontend/ – the React/TypeScript UI (≈4.2 k code files) that renders dashboards, session replays and flag controls.

Supporting projects include:

ProjectCode filesNotable purpose
products/3.1 kIndividual product modules (e.g., analytics, experiments).
nodejs/1.0 kNode‑based services such as the recording rasterizer.
rust/714High‑performance ingestion workers and the “hobby‑installer”.
ee/848Enterprise‑only extensions (feature flags, SSO).
services/402Auxiliary micro‑services (e.g., ingestion pipeline).
cli/69Command‑line tooling (cli/src/main.rs).
packages/119Shared NPM packages (e.g., hogql_parser).
playwright/79End‑to‑end test harness.
……Remaining folders contain docs, CI helpers, and tooling.

Common techniques across the portfolio are Docker‑based deployment, Go‑based installers, Rust workers, and a TypeScript monorepo for the UI. The codebase reuses the HogQL parser (both Python and TS) and shares schema definitions through common/.

How It Is Wired

Execution starts with the Dockerfile at the repository root. It builds a multi‑stage image that:

  1. Installs the Go hobby installer – entry point bin/hobby-installer/main.go. This binary boots the self‑hosted environment, writes runtime config, and then execs the backend server.
  2. Runs the Django app – the container’s CMD (see posthog/Dockerfile) invokes python manage.py runserver (or gunicorn in production). All HTTP traffic enters the Django URL router (posthog/api/urls.py), which forwards to view functions in posthog/api/views.py.
  3. Spawns Rust ingestion workers – the backend launches the compiled binary from rust/ingestion/ via subprocess.Popen. Workers read from the ClickHouse queue, parse events with common/hogql_parser, and write normalized data back to ClickHouse.
  4. Serves the UI – a separate Node image built from frontend/Dockerfile.node runs npm start, serving the compiled React bundle (frontend/src/). API calls from the UI hit the same Django endpoints, completing the full request‑response loop.

The HogQL parser is a hub: common/hogql_parser/package.json (TS) and common/hogql_parser/pyproject.toml (Python) compile to a shared grammar used by both the backend and UI. Changes here ripple through every product module that executes queries.

How To Use It

# Clone the repository
git clone https://github.com/moses-y/posthog
cd posthog

# Build the Docker image (uses the root Dockerfile)
docker build -t posthog:local .

# Run the container (example: expose ports 8000/3000)
docker run -p 8000:8000 -p 3000:3000 posthog:local

Configuration – the container expects an environment file named .env. A template exists at .env.example; copy it and fill in required keys (e.g., POSTHOG_DB_URL, CLICKHOUSE_URL).

Running the UI locally – after the container is up, the React dev server can be started with:

cd frontend
npm install
npm run dev   # serves on http://localhost:3000

The CLI tools are built from Rust sources:

cd cli
cargo build --release
./target/release/posthog-cli --help

Real‑World Use

A SaaS product can drop this Docker image behind its internal network, point its client libraries at http://<host>:8000/api/, and immediately gain event capture, feature flag rollout, and session replay without external subscriptions. The same image can be extended with the ee/ modules for SSO or advanced permissions.

Code Health & Issues

  • High – rust/.env is tracked despite .gitignore; remove with git rm --cached rust/.env and rotate credentials.
  • High – Committed .env files (rust/.env, rust/personhog-replica/.env) expose live secrets; delete, add to .gitignore, and replace with an empty .env.example.
  • Medium – Base images in Dockerfile are not pinned by digest; switch to node:24.13.0-bookworm-slim@sha256:<digest>.
  • Medium – No dependency‑vulnerability scan in CI; add dependency-review-action or osv-scanner to GitHub Actions.
  • Medium – Large static assets (rust/cymbal/tests/static/*.js.map, historical.csv) should be moved to Git LFS or external storage.
  • Medium – Checkout step in .github/workflows/build-hogql-parser-npm.yml keeps the token; set persist-credentials: false.
  • Medium – package.json contains a postinstall script; disable scripts in CI (npm ci --ignore-scripts) or migrate work to explicit build steps.

Additional observations: tests are abundant (≈4.4 k files) and CI is present, but the repository still contains committed secrets and unpinned Docker bases, which pose immediate security risk.

The Bottom Line

PostHog’s codebase is a well‑structured collection of backend, frontend and supporting services that can be self‑hosted with a single Docker image. It offers extensive analytics capabilities out‑of‑the‑box, but the current commit includes serious secret‑leakage and Docker hygiene issues that must be remedied before production use. Engineers comfortable with Django, React and container deployment will find it a solid foundation; teams lacking security review processes should address the high‑severity findings first.