Installation Guide
All environments and dependencies must be managed with
uv.
Complete installation instructions for django-micboard.
System Requirements
Minimum Requirements
- Python: 3.13 or higher
- Django: 5.2 through 6.0
- Database: PostgreSQL for production; SQLite for local development and tests
- Memory: 512MB RAM minimum
- Storage: 100MB free space
Recommended Setup
- Python: 3.13+
- Database: PostgreSQL 13+
- Redis: 6.0+ (for native Huey and WebSocket support)
- Memory: 1GB+ RAM
- Web Server: Nginx + Gunicorn or Apache + mod_wsgi
Installation Methods
Method 1: Add to a Host Project
# Add the package and common integrations to the host project's lockfileuv add "django-micboard[standard,realtime]"
# Or for latest development versionuv add "django-micboard @ git+https://github.com/justprosound/django-micboard.git"Method 2: From Source with UV (RECOMMENDED)
# Clone repositorygit clone https://github.com/justprosound/django-micboard.gitcd django-micboard
# Install the project and every supported optional integrationuv sync --locked --all-extras
# To install optional extras onlyuv sync --locked --extra realtime --extra tasks --extra standardMethod 3: Docker Installation
NOTE: Any Dockerfile or base container for django-micboard MUST use
uvfor all installation steps. All sample Dockerfiles below demonstrate this policy.
version: '3.8'services: micboard: image: django-micboard:latest environment: - DJANGO_SETTINGS_MODULE=myproject.settings - DATABASE_URL=postgresql://user:pass@db:5432/micboard ports: - "8000:8000" depends_on: - db - redis
db: image: postgres:13 environment: - POSTGRES_DB=micboard - POSTGRES_USER=user - POSTGRES_PASSWORD=pass
redis: image: redis:6-alpineDjango Configuration
Basic Setup
Add to your Django settings.py. Keep Django’s built-in SecurityMiddleware enabled and
configure any Content Security Policy in the host project; Micboard does not replace host
security headers.
import os
DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"
INSTALLED_APPS = [ # Django core apps 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',
# Third-party apps 'channels', 'huey.contrib.djhuey',
# Micboard 'micboard',]
# Database configurationDATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'micboard', 'USER': 'micboard_user', 'PASSWORD': 'secure_password', 'HOST': 'localhost', 'PORT': '5432', }}
# Native Huey Django integration. Use immediate mode only for local development/tests.HUEY = { "huey_class": "huey.RedisHuey", "name": "micboard", "connection": { "url": os.environ.get("REDIS_URL", "redis://localhost:6379/1"), }, "immediate": DEBUG,}
# InternationalizationLANGUAGE_CODE = 'en-us'TIME_ZONE = 'UTC'USE_I18N = TrueUSE_TZ = True
# Static filesSTATIC_URL = 'static/'STATIC_ROOT = '/var/www/micboard/static/'
# Media files (optional)MEDIA_URL = 'media/'MEDIA_ROOT = '/var/www/micboard/media/'Shure API Configuration
import os
# Shure System API settings use the shared key issued by Shure System API.MICBOARD_CONFIG = { "SHURE_API_BASE_URL": os.environ.get( "MICBOARD_SHURE_API_BASE_URL", "https://your-shure-system.local:10000" ), "SHURE_API_SHARED_KEY": os.environ.get("MICBOARD_SHURE_API_SHARED_KEY"), "SHURE_API_TIMEOUT": int(os.environ.get("MICBOARD_SHURE_API_TIMEOUT", "30")),}
# Exact hostnames that credential-bearing Manufacturer API Server checks may contact.# Do not include schemes, ports, paths, or wildcards.MICBOARD_API_SERVER_ALLOWED_HOSTS = ["your-shure-system.local"]The package reads Django settings rather than environment variables directly. Host projects may
use different environment names, but must map values into MICBOARD_CONFIG themselves.
The API-server allowlist is enforced for admin connection checks so an editable URL cannot send a
manufacturer credential to an arbitrary destination.
Channels Configuration (WebSocket)
# ASGI applicationASGI_APPLICATION = 'myproject.asgi.application'
# Channel layers for WebSocket supportCHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { 'hosts': [('127.0.0.1', 6379)], }, },}Security Settings
# Security settingsSECRET_KEY = 'your-very-secure-secret-key-here'DEBUG = FalseALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com']
# HTTPS settings (recommended for production)SECURE_SSL_REDIRECT = TrueSECURE_HSTS_SECONDS = 31536000SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = True
# Session securitySESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueOptional Features
# Standard Django email configuration for host-project notificationsEMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'EMAIL_HOST = 'smtp.gmail.com'EMAIL_PORT = 587EMAIL_USE_TLS = TrueEMAIL_HOST_USER = 'your-email@gmail.com'EMAIL_HOST_PASSWORD = 'your-app-password'
# LoggingLOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': '/var/log/micboard/django.log', }, }, 'loggers': { 'micboard': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, },}ASGI Configuration
Update your asgi.py:
import os
from channels.auth import AuthMiddlewareStackfrom channels.routing import ProtocolTypeRouter, URLRouterfrom channels.security.websocket import AllowedHostsOriginValidatorfrom django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")django_asgi_app = get_asgi_application()
# Import micboard WebSocket routesfrom micboard.websockets.routing import websocket_urlpatterns
application = ProtocolTypeRouter({ "http": django_asgi_app, "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack(URLRouter(websocket_urlpatterns)) ),})Database Setup
PostgreSQL (Required for Production)
PostgreSQL is required when DEBUG=False. django-micboard’s deployment system check rejects
other database engines in production because cross-model IP ownership relies on PostgreSQL
transaction advisory locks. SQLite remains supported for local development and tests.
# Create database and usersudo -u postgres psqlCREATE DATABASE micboard;CREATE USER micboard_user WITH PASSWORD 'secure_password';GRANT ALL PRIVILEGES ON DATABASE micboard TO micboard_user;ALTER USER micboard_user CREATEDB;\qSQLite (Development Only)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', }}Initial Setup
Run Migrations
# Apply database migrationsuv run --no-sync python manage.py migrate
# Create superuseruv run --no-sync python manage.py createsuperuserCollect Static Files
# Collect static files for productionuv run --no-sync python manage.py collectstatic --noinputVerify Installation
# Run the package test suite from a source checkoutuv run --no-sync pytest
# Check system healthuv run --no-sync python manage.py check
# Test Shure API connection (if configured)uv run --no-sync python manage.py diagnostic_api_health_checkProduction Deployment
Gunicorn + Nginx
Install dependencies:
uv add gunicornsudo apt install nginxGunicorn configuration:
# Create systemd servicesudo nano /etc/systemd/system/micboard.service[Unit]Description=Micboard Django ApplicationAfter=network.target
[Service]User=www-dataGroup=www-dataWorkingDirectory=/var/www/micboardEnvironment="DJANGO_SETTINGS_MODULE=myproject.settings"ExecStart=/usr/local/bin/uv run --no-sync gunicorn --workers 3 --bind unix:/var/www/micboard/micboard.sock myproject.wsgi:applicationRestart=always
[Install]WantedBy=multi-user.targetNOTE: All virtual environments in this project must be created with
uv. SetExecStartto the absolute path returned bycommand -v uvon the deployment host.
Nginx configuration:
server { listen 80; server_name your-domain.com www.your-domain.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ { alias /var/www/micboard/static/; }
location /media/ { alias /var/www/micboard/media/; }
location / { include proxy_params; proxy_pass http://unix:/var/www/micboard/micboard.sock; }
# WebSocket support (only needed when you serve Micboard over ASGI) location = /ws { proxy_pass http://unix:/var/www/micboard/micboard.sock; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# Micboard never sends unsolicited traffic on a quiet connection, so an idle # WebSocket looks dead to a proxy. Nginx defaults proxy_read_timeout to 60s and # would drop the connection after a minute of quiet; raise it past the longest # gap you expect between broadcasts, and past your client's keepalive interval. proxy_read_timeout 3600s; proxy_send_timeout 3600s; }}Enable site and restart services:
sudo ln -s /etc/nginx/sites-available/micboard /etc/nginx/sites-enabledsudo nginx -tsudo systemctl restart nginxsudo systemctl enable micboardsudo systemctl start micboardWhat a reverse proxy in front of Micboard carries
Micboard has two delivery modes, and they put very different loads on a proxy.
HTTP polling is the default and needs no configuration. Every live browser surface — alerts, assignments, the charger grid, and kiosk walls — refreshes itself with a short request on a timer. These are ordinary, short-lived HTTP requests: nothing is held open, so no connection limit applies beyond your normal request capacity. What does scale is request rate. Refresh interval multiplied by open tabs is the whole volume, and each interval is a setting (see Configuration), so slowing a busy deployment down is a settings change rather than a template fork.
WebSocket push is opt-in and holds a connection open per client. It is active only when
you install Channels, point ASGI_APPLICATION at a ProtocolTypeRouter that includes
micboard.websockets.routing.websocket_urlpatterns, configure CHANNEL_LAYERS, and serve the
project with an ASGI server. If any of those is missing, Micboard’s system checks report it
and browsers keep polling. When it is active, each connected client holds one upstream
connection for as long as the page is open, so size your proxy’s connection limits for peak
concurrent viewers rather than for request rate.
Two behaviours matter for keeping those connections alive:
- Micboard sends nothing on a quiet connection. There is no server-initiated keepalive frame. A proxy that closes idle upstream connections will close a healthy but quiet Micboard connection, so its idle timeout must exceed the longest expected gap between broadcasts.
- The client drives the keepalive. A client may send
{"command": "ping"}and Micboard replies{"type": "pong"}. Send it on an interval comfortably shorter than the proxy’s idle timeout. Keep it well underMICBOARD_WEBSOCKET_COMMANDS_PER_MINUTE(default 60); a connection that exceeds its allowance is closed with code4429.
Traefik configuration:
Traefik upgrades WebSocket connections without extra configuration, but its default
respondingTimeouts will close an idle one. Raise the read and idle timeouts on the
entrypoint that serves Micboard:
# traefik static configurationentryPoints: websecure: address: ":443" transport: respondingTimeouts: # 0 disables the limit entirely; prefer an explicit ceiling over disabling it. readTimeout: 0 idleTimeout: 3600s# traefik dynamic configurationhttp: serversTransports: micboard: forwardingTimeouts: dialTimeout: 30s responseHeaderTimeout: 0 services: micboard: loadBalancer: serversTransport: micboard servers: - url: "http://micboard:8000"If you run more than one Micboard process behind Traefik, use a shared channel layer
(channels_redis) so a broadcast produced by one process reaches clients connected to
another, and prefer sticky sessions only if your client cannot tolerate reconnecting.
Docker Deployment
Dockerfile:
FROM python:3.13-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.11.28 /uv /uvx /bin/
WORKDIR /app
COPY pyproject.toml uv.lock ./RUN uv sync --locked --no-install-project
COPY . .RUN uv sync --locked
RUN uv run --no-sync python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["uv", "run", "--no-sync", "gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]NOTE: The image copies a pinned binary from Astral’s official uv image and installs the locked project with
uv sync.
Build and run:
docker build -t micboard .docker run -p 8000:8000 micboardPost-Installation Setup
Device Discovery
# Expand CIDRs already configured in the admin discovery settingsuv run --no-sync python manage.py sync_discovery --manufacturer shure --scan-cidrs
# Or add specific devicesuv run --no-sync python manage.py discovery_add_devices --ips 192.168.1.100,192.168.1.101Start Monitoring
# Initial device polluv run --no-sync python manage.py poll_devices --manufacturer shure
# Enqueue one poll through native Hueyuv run --no-sync python manage.py poll_devices --manufacturer shure --asyncAdmin Configuration
- Access
/admin/with superuser credentials - Configure user permissions
- Set up device assignments
- Configure alert thresholds
Troubleshooting Installation
Import Errors
Module not found:
# Ensure the lockfile and environment are synchronizeduv sync --locked --all-extras
# Check Python pathuv run --no-sync python -c "import micboard; print(micboard.__file__)"Use
uv syncwith the project lockfile. Do not install this project from ad hoc requirement files.
Database Errors
Migration failures:
# Inspect migration state without rewriting migration historyuv run --no-sync python manage.py showmigrations micboarduv run --no-sync python manage.py migrate --plan
# Check database connectivityuv run --no-sync python manage.py dbshellPermission Errors
Static file issues:
# Fix permissionssudo chown -R www-data:www-data /var/www/micboard/sudo chmod -R 755 /var/www/micboard/WebSocket Issues
Connection failures:
# Check Redis connectivityredis-cli ping
# Verify ASGI configurationuv run --no-sync python manage.py checkNext Steps
- Quick Start Guide - Get monitoring quickly
- Configuration - Detailed configuration options
- Shure Integration - Shure System API setup
- Admin Interface - Using the admin dashboard
