Shynet provides self-hosted, cookie-free web analytics as an alternative to Google Analytics, tracking site hits via lightweight scripts or pixel beacons.
Web development shops managing multiple client properties face a recurring analytics dilemma: pay mounting per-site SaaS fees or subject visitors to intrusive tracking cookies and consent banners. Shynet resolves this conflict by providing a self-hosted, cookie-free analytics backend deployed via Docker or Kubernetes. The platform captures essential traffic patterns while keeping all visitor telemetry inside private infrastructure.
Google Analytics routes visitor telemetry through Google Tag Manager and advertising networks, requiring complex consent banners, privacy disclosures, and compliance agreements. For devops teams and agency leads who simply need to know which client pages attract readers or where referrals originate, navigating GA4 exploration reports introduces needless workflow overhead. Shynet embeds directly into a team's existing infrastructure. Its sub-kilobyte script runs without cookies, and an optional 1x1 image pixel records hits even when visitors block JavaScript.
The trade-off is focused traffic metrics versus commercial marketing depth. Google Analytics provides multi-channel attribution, predictive cohorts, and direct ad synchronization. Shynet tracks foundational indicators: unique sessions, page hits, referrers, bounce rates, durations, and device profiles. Migrating from SaaS tools like Fathom Analytics or Google Analytics requires managing a PostgreSQL container, but it eliminates recurring per-site invoices and keeps client data confidential.
Shynet vs Google Analytics — which should a team pick? Choose Google Analytics for multi-channel ad attribution, custom event goals, and ad campaign integration. Choose Shynet for lightweight, cookie-free traffic statistics hosted entirely on private infrastructure without cookie popups.
Is Shynet free for commercial use? Yes. Distributed under the Apache-2.0 license, Shynet allows companies and agencies to monitor client properties without software licensing fees.
Can Shynet track traffic without JavaScript enabled? Yes. Shynet provides a 1x1 transparent image pixel that records page hits from script-blocked browsers, feed readers, or markdown documents.
For small teams and agencies seeking lightweight, cookie-free web metrics without recurring SaaS bills, Shynet offers an effective self-hosted solution provided your staff can maintain container deployments. Its Apache-2.0 terms permit unrestricted use across client properties, while maintenance updates through March 2026 ensure active repository health.
We started this exact setup from scratch in a clean Linux machine on Nov 3, 2026 and checked it through Shynet's own API: every check passed. Recipes are re-checked regularly and removed if they stop working.
mkdir -p shynet && cd shynet
curl -fsSL https://open-source.store/tools/shynet/compose.yml -o compose.ymlcat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 12)
DJANGO_SECRET_KEY=$(openssl rand -hex 32)
ALLOWED_HOSTS=*
CSRF_TRUSTED_ORIGINS=http://localhost:8080
EOFPOSTGRES_PASSWORD — Password for the bundled PostgreSQL database user. Generated above; keep it secret.DJANGO_SECRET_KEY — Secret key for Django cryptographic signing and sessions. Generated above; keep it secret.ALLOWED_HOSTS — Comma-separated hostnames Shynet is allowed to serve; set to your public domain in production.CSRF_TRUSTED_ORIGINS — Comma-separated list of trusted origins for CSRF protection, including scheme and port (e.g. http://localhost:8080 or https://analytics.example.com).docker compose up -dservices:
shynet:
image: milesmcc/shynet:v0.13.1
ports:
- "8080:8080"
environment:
DB_NAME: shynet
DB_USER: shynet
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: db
DB_PORT: 5432
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
ALLOWED_HOSTS: ${ALLOWED_HOSTS}
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS}
ACCOUNT_SIGNUPS_ENABLED: "False"
SCRIPT_USE_HTTPS: "False"
PORT: 8080
PERFORM_CHECKS_AND_SETUP: "True"
depends_on:
db:
condition: service_healthy
restart: always
healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/healthz/ || exit 1"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: shynet
POSTGRES_USER: shynet
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- shynet-db-data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
volumes:
shynet-db-data:
Lint, codegen, breaking-change checks and dependency management in one Git-native CLI. Open source, no registry lock-in.
Show that Shynet is listed as an open-source alternative. Paste into your README:
Ubuntu 24.04 · Apple container, Docker 29.1.3, Compose 2.40.3+ds1-0ubuntu1~24.04.1. Images pulled in 216 s: milesmcc/shynet:v0.13.1, postgres:15-alpine.
Based on the upstream setup. Official compose with db and app. Image pinned to release v0.13.1 and Postgres to 15-alpine; dropped the example reverse-proxy nginx service so Shynet serves HTTP directly on port 8080; database credentials and secrets moved to .env.
shynet-1 | 2026-11-03T08:30:44.793366380Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.793368130Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.793369713Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.793371380Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.793373005Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.793374797Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.793376338Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.793378088Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.793379505Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.793381380Z Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.793383130Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.793384630Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.793386422Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.793387922Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.793389547Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.793391255Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.793392880Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.793394255Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.793396005Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.793397588Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.813654922Z WARNING Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:30:44.813682380Z Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:30:44.814026838Z WARNING Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:30:44.814028672Z Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:30:44.832945922Z ERROR Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832963755Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.832964963Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.832965797Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.832966380Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.832967047Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.832967588Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.832968213Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.832968755Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.832969338Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.832969880Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832970547Z Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832971172Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.832971713Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.832972297Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.832972797Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.832973380Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.832973880Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.832974505Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.832974963Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.832975547Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.832976047Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832976672Z ERROR Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832977255Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.832977713Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.832984963Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.832985588Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.832986172Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.832986755Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.832987297Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.832987797Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.832988380Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.832988880Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832989505Z Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832990088Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.832990588Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.832991172Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.832991713Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.832992380Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.832992880Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.832993463Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.832993922Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.832994505Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.832994963Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832995588Z ERROR Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.832996172Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.832996630Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.832997172Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.832997672Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.832998213Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.832998838Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.832999422Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.833000755Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.833001338Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.833001838Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833002422Z Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833003005Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.833003505Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.833004047Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.833004547Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.833005088Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.833005588Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.833006130Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.833006630Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.833007172Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.833007672Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833138172Z ERROR Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833140755Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.833141380Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.833142005Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.833142505Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.833143088Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.833143672Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.833144255Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.833144755Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.833145297Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.833145797Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833146380Z Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:30:44.833146963Z Traceback (most recent call last): shynet-1 | 2026-11-03T08:30:44.833149088Z File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner shynet-1 | 2026-11-03T08:30:44.833149713Z response = get_response(request) shynet-1 | 2026-11-03T08:30:44.833150213Z File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 135, in __call__ shynet-1 | 2026-11-03T08:30:44.833150755Z response = self.process_request(request) shynet-1 | 2026-11-03T08:30:44.833151255Z File "/usr/local/lib/python3.10/site-packages/django/middleware/common.py", line 48, in process_request shynet-1 | 2026-11-03T08:30:44.833151797Z host = request.get_host() shynet-1 | 2026-11-03T08:30:44.833152297Z File "/usr/local/lib/python3.10/site-packages/django/http/request.py", line 152, in get_host shynet-1 | 2026-11-03T08:30:44.833152797Z raise DisallowedHost(msg) shynet-1 | 2026-11-03T08:30:44.833153297Z django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.65.7:8080,oss-verify.example'. The domain name provided is not valid according to RFC 1034/1035. shynet-1 | 2026-11-03T08:31:10.875349336Z WARNING Forbidden (CSRF cookie not set.): /admin/ shynet-1 | 2026-11-03T08:31:10.875377586Z Forbidden (CSRF cookie not set.): /admin/ shynet-1 | 2026-11-03T08:31:10.875919586Z WARNING Forbidden (CSRF cookie not set.): /admin/ shynet-1 | 2026-11-03T08:31:10.875928752Z Forbidden (CSRF cookie not set.): /admin/ shynet-1 | 2026-11-03T08:31:10.900902461Z WARNING Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:31:10.900915044Z Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:31:10.904441461Z WARNING Forbidden (CSRF cookie not set.): / shynet-1 | 2026-11-03T08:31:10.904451086Z Forbidden (CSRF cookie not set.): /