How to Self-Host Shynet with Docker Compose
What Is Shynet?
Shynet is a privacy-focused, cookie-free web analytics platform that tracks page views and sessions without JavaScript (it uses a tracking pixel by default, with an optional JS snippet for richer data). It replaces Google Analytics for users who want accurate visitor counts without compromising privacy. Built with Django and open-source under the Apache 2.0 license, Shynet provides a clean dashboard showing page views, sessions, referrers, and device information — all without cookies or fingerprinting.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 512 MB of free RAM (minimum)
- 2 GB of free disk space
- A domain name (optional, for remote access)
Docker Compose Configuration
Create a directory for your Shynet deployment:
mkdir -p ~/shynet && cd ~/shynet
Create a docker-compose.yml file:
services:
shynet:
image: milesmcc/shynet:v0.13.1
container_name: shynet
depends_on:
db:
condition: service_healthy
environment:
# Database connection
DB_NAME: shynet
DB_USER: shynet
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: db
DB_PORT: "5432"
# Django settings — CHANGE the secret key
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
# Your domain (no trailing slash)
ALLOWED_HOSTS: ${SHYNET_HOST:-*}
CSRF_TRUSTED_ORIGINS: ${CSRF_ORIGINS:-https://analytics.example.com}
# Application settings
PORT: "8080"
PERFORM_CHECKS_AND_SETUP: "True"
SCRIPT_USE_HTTPS: "True"
ACCOUNT_SIGNUPS_ENABLED: "False"
TIME_ZONE: "UTC"
ports:
- "8080:8080"
restart: unless-stopped
networks:
- shynet-net
db:
image: postgres:16-alpine
container_name: shynet-db
environment:
POSTGRES_DB: shynet
POSTGRES_USER: shynet
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- shynet_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shynet"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- shynet-net
volumes:
shynet_db:
networks:
shynet-net:
driver: bridge
Create a .env file alongside it:
# Database password — CHANGE THIS to a strong random value
DB_PASSWORD=change-me-to-a-strong-random-password
# Django secret key — generate with: python3 -c "import secrets; print(secrets.token_urlsafe(50))"
DJANGO_SECRET_KEY=change-me-to-a-long-random-string
# Your Shynet domain (used for ALLOWED_HOSTS and CSRF)
SHYNET_HOST=analytics.example.com
CSRF_ORIGINS=https://analytics.example.com
Start the stack:
docker compose up -d
Initial Setup
After the containers start, create your admin account:
docker exec -it shynet ./manage.py registeradmin [email protected]
You’ll be prompted to set a password. Then set the instance name:
docker exec -it shynet ./manage.py whitelabel "My Analytics"
Access the web UI at http://your-server-ip:8080 and log in with the email and password you just created.
To add a site for tracking:
- Click “Create a Service” in the dashboard
- Enter your site name and URL
- Copy the tracking pixel (
<img>tag) or JavaScript snippet - Add it to your website’s HTML
| Tracking Method | Pros | Cons |
|---|---|---|
Tracking pixel (<img>) | No JavaScript needed, works everywhere | No session duration or page load times |
| JavaScript snippet | Session data, page load metrics, SPA support | Requires JS execution |
Configuration
Email Notifications (Optional)
Add SMTP settings to receive alerts about your analytics:
environment:
EMAIL_HOST: smtp.example.com
EMAIL_PORT: "587"
EMAIL_HOST_USER: [email protected]
EMAIL_HOST_PASSWORD: ${SMTP_PASSWORD}
EMAIL_USE_TLS: "True"
SERVER_EMAIL: [email protected]
Multi-Instance Scaling with Redis
For high-traffic sites, add Redis for caching and Celery task processing:
services:
redis:
image: redis:7-alpine
container_name: shynet-redis
restart: unless-stopped
networks:
- shynet-net
shynet:
environment:
REDIS_CACHE_LOCATION: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/1
CELERY_TASK_ALWAYS_EAGER: "False"
Key Environment Variables
| Variable | Default | Description |
|---|---|---|
ACCOUNT_SIGNUPS_ENABLED | False | Allow public account registration |
SCRIPT_USE_HTTPS | True | Use HTTPS in tracking scripts |
PERFORM_CHECKS_AND_SETUP | True | Auto-run migrations on startup |
SHOW_SHYNET_VERSION | True | Display version in footer |
TIME_ZONE | UTC | Dashboard timezone |
Reverse Proxy
Place Shynet behind a reverse proxy for SSL termination. With Nginx Proxy Manager:
- Scheme:
http - Forward Hostname:
shynet(container name) or your server IP - Forward Port:
8080 - Enable SSL and force HTTPS
Set CSRF_TRUSTED_ORIGINS to your public URL (e.g., https://analytics.example.com). See our Reverse Proxy Setup guide for detailed configuration.
Backup
Back up the PostgreSQL database regularly:
docker exec shynet-db pg_dump -U shynet shynet > shynet_backup_$(date +%Y%m%d).sql
The shynet_db volume contains all persistent data. Include it in your backup strategy.
Troubleshooting
CSRF Verification Failed
Symptom: 403 Forbidden error when logging in.
Fix: Ensure CSRF_TRUSTED_ORIGINS matches your exact public URL including the protocol (https://analytics.example.com). Restart the container after changing it.
Tracking Pixel Not Recording Visits
Symptom: Dashboard shows zero hits despite pixel placement.
Fix: Verify the pixel URL is reachable from the client browser. Check that ALLOWED_HOSTS includes your domain. If using HTTPS, set SCRIPT_USE_HTTPS=True.
Database Connection Refused
Symptom: Shynet fails to start with “could not connect to server” errors.
Fix: Ensure the db container is healthy (docker compose ps). The depends_on with health check should handle startup order, but PostgreSQL may need more time on first run.
Admin Account Locked Out
Symptom: Forgot admin password. Fix: Reset via Django management command:
docker exec -it shynet ./manage.py changepassword [email protected]
Resource Requirements
- RAM: ~150 MB idle, ~300 MB under moderate load
- CPU: Low — Django handles analytics ingestion efficiently
- Disk: ~500 MB for application, plus database growth (~1 GB per million page views)
Verdict
Shynet is an excellent choice if you want dead-simple, privacy-respecting analytics without the complexity of Matomo or the JavaScript dependency of Plausible. The tracking pixel approach means it works on sites where JavaScript is blocked or unwanted. The trade-off is fewer features — no funnel analysis, no real-time dashboards, no custom events. For most personal sites and small projects, that’s a fair trade. If you need product analytics or advanced segmentation, look at PostHog instead.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments