PixelCast is a self-hosted or SaaS digital signage system: Django REST back end, real-time channels, a Vue 3
admin SPA, and a browser-based web player. This page describes how the repository is structured, how to run and
configure it, and where to find APIs and operational tools.
Security: Never commit a real .env file. Use strong SECRET_KEY and
DB_PASSWORD values in production, and keep DEBUG=False outside local development.
Docker: Development uses docker-compose.yml (Vite + Uvicorn hot reload). Production
uses docker-compose.prod.yml (Nginx + Gunicorn/Uvicorn). The production frontend image serves this
HTML under /documentation/.
1) Introduction
You can manage screens, templates, media, schedules, and device commands from the admin UI. Devices and web
players talk to the same back end over HTTPS and WebSockets; heavy work (email, SMS, billing webhooks) runs
through Celery workers backed by Redis.
Browser / player
→
Nginx (SPA + proxy)
→
Django (Gunicorn + Uvicorn)
→
PostgreSQL
+
Redis
2) How this documentation is served
Canonical copies of these pages live under documentation/ at the repository root. The same HTML and
CSS are mirrored into frontend/public/documentation/ so Vite (dev) and the production build emit them
under /documentation/ (e.g. /documentation/index.html). Nginx in the frontend
container serves that path; short URLs /docs and /docs/changelog in the SPA redirect to
the static files. After editing files under documentation/, copy them into
frontend/public/documentation/ (or rebuild from a CI step that syncs both).
Files in the package
documentation/ # source (edit here)
├── index.html
├── changelog.html
└── assets/
├── style.css
└── screenshots/ # optional; figures hide if missing
frontend/public/documentation/ # shipped with the SPA build
For marketing and long-form guides, use the in-app blog. For interactive REST exploration,
authenticated operators can use OpenAPI / Swagger at /api/docs/ (see below).
Nginx terminates HTTP, serves the SPA and static docs, and reverse-proxies /api/, /iot/, /ws/, /media/, etc.
The Django settings package is Screengram under app/Screengram/ (ROOT_URLCONF
is Screengram.urls); product branding in the UI is PixelCast. IoT routes are mounted at
/iot/ and (for backward compatibility) /public-iot/, both including the same
signage URLs, while authenticated app traffic also uses /api/… for signage and related
endpoints.
Open the Vite dev app at http://localhost:5173. Django is available through the proxy at
same-origin /api/ — do not point the browser at http://backend:8000 (that hostname
exists only inside the Compose network).
6b) Production with Docker
docker compose -f docker-compose.prod.yml up -d --build
The production compose file maps the frontend container to host port 8080
(8080:80 on the frontend service). To use another host port, change that mapping (or
terminate TLS on a reverse proxy and do not publish the container port publicly). Both frontend and
backend attach to the external network dokploy-network when present—create it on the
host if you deploy behind Traefik/Dokploy. Static documentation is baked into the frontend image from
frontend/public/documentation/; avoid bind-mounting documentation/ unless you control
that path on every host.
6c) Installer script
chmod +x install.sh && ./install.sh
6d) First-run wizard
After containers are healthy, open /install in the browser to complete database checks and create
the first administrator. Setup APIs live under /api/setup/.
dbPostgreSQL 15 with a persistent volume
redisCache, Celery broker, Channels layer
backendDjango ASGI, migrations on boot
frontendDev: Vite; Prod: Nginx + built SPA + this HTML
7) Configuration (.env)
Compose loads only .env from the project root; .env.example is a template and is not
loaded automatically. Mirror DB_* credentials into POSTGRES_* for the database
container when using the bundled Postgres service.
# — excerpt; see .env.example for the full list —
# Django
SECRET_KEY=<generate-a-long-random-string>
DEBUG=False
ALLOWED_HOSTS=your.domain,localhost,127.0.0.1
CSRF_TRUSTED_ORIGINS=https://your.domain
BASE_URL=https://your.domain
# Database (Docker service name is usually `db`)
DB_HOST=db
DB_NAME=pixelcast_signage_db
DB_USER=pixelcast_signage_user
DB_PASSWORD=<your-secure-password>
DB_PORT=5432
# Redis / Celery
REDIS_HOST=redis
REDIS_PORT=6379
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
USE_REDIS_CACHE=True
# Deployment mode: saas | self_hosted | hybrid
DEPLOYMENT_MODE=hybrid
PLATFORM_SAAS_ENABLED=True
# Stripe (SaaS billing — optional per deployment)
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
# License / marketplace (see .env.example for full registry + grace tuning)
LICENSE_ENFORCEMENT_ENABLED=True
LICENSE_GATEWAY_BASE_URL= # self-hosted installs (operator ↔ license service)
LICENSE_SERVER_URL= # SaaS / gateway URL when applicable
CODECANYON_TOKEN=
# Optional integrations
OPENWEATHER_API_KEY= # weather widget in templates
SSO_ENABLED=False # SSO-ready account flows when True
Never commit real secretsUse UTC on servers for schedulesSet PUBLIC_WEB_APP_URL for email linksANDROID_TV_APK_URL → GET /api/public/downloads/CHANNEL_LAYERS_BACKEND=redis in real deployments
8) URLs, API & OpenAPI
These paths are defined in app/Screengram/urls.py and related URLconfs.
/super-admin/… — platform operator console (when your account is allowed)
API groups (see app/Screengram/urls.py)
/api/setup/ # installation wizard
/api/health/ # JSON health probe
/api/public/downloads/ # Android TV APK URL JSON
/api/public/deployment/ # deployment metadata
/api/public/pricing/ # public pricing snapshot
/api/public/blog/ # public blog feed/content
/api/license/ # license activation & enforcement API
/api/license-registry/v1/ # license registry (telemetry / admin integrations)
/api/platform/blog/ # platform blog admin API
/api/platform/tickets/ # platform ticket admin API
/api/platform/ # SaaS super-admin (tenants, Stripe, reporting…)
/api/auth/… # JWT, sessions, invitations (accounts.urls)
/api/… # signage, templates, commands, bulk_operations,
# content_validation, analytics (see each app’s urls)
/api/tickets/ # requester helpdesk API
/api/core/ # audit, backup, infrastructure
/api/logs/ # centralized error log ingestion
/api/admin/errors/ # admin error log API (DRF router)
/iot/ … # same signage IoT routes, same-origin for browsers
/public-iot/ … # legacy alias for signage IoT URLs
/qr/<slug>/ # QR action redirects (templates)
/api/docs/ # Swagger UI (authenticated)
/api/redoc/ # ReDoc (authenticated)
/api/schema/ # OpenAPI schema
Administrative API documentation is protected — you must sign in as a user who is allowed to view schema (see
api_docs views). The JSON health check is lightweight and suitable for uptime monitors.
9) WebSockets & IoT paths
WebSockets: Nginx proxies /ws/ to Django Channels (upgrade headers in frontend/nginx.conf). Configure CHANNEL_LAYERS_BACKEND (and Redis) for production-style multi-worker setups.
IoT: Browsers and players should call same-origin /iot/… (build-time VITE_IOT_BASE_URL=/iot in Docker) so hostnames like backend:8000 never appear in the client.
Media: With local disk storage, Django serves uploads under /media/ even when DEBUG=False; Nginx is configured to proxy that path to the backend.
10) SaaS & deployment modes
DEPLOYMENT_MODE=self_hosted — single-tenant style; platform SaaS features follow settings.
DEPLOYMENT_MODE=saas — multi-tenant operation with super-admin tooling.
DEPLOYMENT_MODE=hybrid — common middle ground; PLATFORM_SAAS_ENABLED gates Stripe, tenant admin, and related APIs.
Stripe Checkout, customer portal, and webhooks are integrated under saas_platform. Per-plan Stripe
Price IDs can be managed from the Super Admin pricing catalog (database-backed), not only from a single legacy
env var.
Edge logs: Nginx access and error logs inside the frontend container.
Updates: Back up the database and media, pull new images or files, run migrations, rebuild the
SPA if front-end assets changed, then restart services. Always read changelog.html
for breaking changes.
Static assets / 500 errors after deploy
If the UI loads but assets fail, rebuild the front end and ensure Nginx is serving the new dist output.
If API calls return 500, verify environment variables — especially database connectivity and SECRET_KEY.
13) Troubleshooting
net::ERR_NAME_NOT_RESOLVED for backend:8000: The browser cannot resolve
Docker service names. The SPA must call same-origin /api (and /iot for device traffic)
so Vite (dev) or Nginx (prod) proxies to Django. Rebuild the front end after changing any VITE_*
variable and hard-refresh.
WebSocket disconnects: confirm Redis is healthy, /ws/ is proxied with upgrade headers, and firewalls allow long-lived connections.
CSRF errors on login: add your exact origin (scheme + host + port) to CSRF_TRUSTED_ORIGINS.
CORS: additional origins can be set via CORS_ALLOWED_ORIGINS in env (merged with defaults in settings).
14) Running tests
# Backend (pytest inside backend container)
docker compose exec backend pytest --cov
# Frontend unit tests
cd frontend && npm run test
# Frontend e2e
cd frontend && npx playwright test
15) FAQ
Is SQLite supported?
Yes for local experimentation via USE_SQLITE=True. Production should use PostgreSQL.
Where is the OpenAPI schema?
JSON/YAML at /api/schema/; interactive docs at /api/docs/ once authenticated.
Are front-end route guards a security boundary?
No. Authorization is enforced in Django and DRF. Hiding a menu item in Vue is UX only.
16) Support & changelog
Use the support channel provided with your license or marketplace purchase (for example, item comments on
CodeCanyon). Custom development, third-party integrations, and managed hosting are outside standard item support
unless separately agreed.