5.3 KiB
Flask Digital Signage (simple)
Lightweight digital signage platform using Flask + SQLite.
Features
- Central admin can manage companies, users, displays.
- Admin can impersonate any company user (no password).
- Company users can:
- Create playlists
- Add slides (image/video/webpage)
- Assign playlists to displays
- Displays are public 16:9 player webpages suitable for kiosk browsers.
Quickstart (Windows)
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
set FLASK_APP=app
flask init-db --admin-email beheer@alphen.cloud --admin-pass admin
flask run --debug
If Flask can't discover the app automatically, use:
set FLASK_APP=app:create_app
flask run --debug
Production (WSGI)
This repo includes a wsgi.py entrypoint for production WSGI servers.
Important (Windows)
If you try to run Gunicorn directly on Windows you will see an error like:
ModuleNotFoundError: No module named 'fcntl'
That’s expected: Gunicorn is Unix-only. On Windows, run the app via:
- Docker (recommended) so Gunicorn runs inside a Linux container, or
- WSL2/Linux (Gunicorn works), or
- use a Windows-native WSGI server (e.g. Waitress) instead of Gunicorn.
Examples:
# gunicorn (Linux)
gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app
# uWSGI
uwsgi --http :8000 --wsgi-file wsgi.py --callable app
Note: unlike flask run, WSGI servers typically don't auto-load .env / .flaskenv.
wsgi.py attempts to load .env (best-effort), but for real production you should set
environment variables via your process manager / secrets.
Docker
Docker Compose (recommended)
This repo includes a docker-compose.yml for a one-command startup.
docker compose up --build
Run in the background:
docker compose up -d --build
Stop:
docker compose down
Data persistence:
- SQLite DB is mounted to
./instanceon your host - uploads are mounted to
./app/static/uploadson your host
Build:
docker build -t signage:latest .
Run (with persistent SQLite DB + uploads):
docker run --rm -p 8000:8000 \
-e SECRET_KEY="change-me" \
-v %cd%/instance:/app/instance \
-v %cd%/app/static/uploads:/app/app/static/uploads \
signage:latest
PowerShell variant (sometimes volume path quoting is easier):
docker run --rm -p 8000:8000 `
-e SECRET_KEY="change-me" `
-v "${PWD}/instance:/app/instance" `
-v "${PWD}/app/static/uploads:/app/app/static/uploads" `
signage:latest
Then open: http://127.0.0.1:8000
Notes:
- The container starts with Gunicorn using
wsgi:app. - You can override Gunicorn settings via env vars:
GUNICORN_WORKERS(default: 2)GUNICORN_BIND(default:0.0.0.0:8000)
Notes
- SQLite DB is stored at
instance/signage.sqlite. - Uploaded files go to
app/static/uploads/.
Display player
Open:
http://<host>/display/<token>for live playback (counts towards the concurrent display limit)http://<host>/display/<token>?preview=1for preview (does not count towards the concurrent display limit)
Live updates
The player keeps itself up-to-date automatically:
- It listens to
GET /api/display/<token>/events(Server-Sent Events) and reloads the playlist immediately when it changes. - It also does a fallback playlist refresh every 5 minutes for networks/proxies that block SSE.
SMTP / Forgot password
This project includes a simple forgot password flow. SMTP configuration is read from environment variables.
You can also configure SMTP settings from the UI: Admin → Settings. Environment variables still take precedence over the database settings.
Public domain for emails
If your app runs behind a reverse proxy (or the internal hostname differs from the public hostname),
set Admin → Settings → Public domain to e.g. signage.example.com so links in password reset
emails point to the correct address.
Recommended: put these in a local .env file in the repo root. Flask (via python-dotenv) will auto-load it on startup. .env is already gitignored.
You can start from .env.example:
copy .env.example .env
Example
REM Option A: set env vars in the same terminal where you run `flask run`
set SMTP_HOST=smtp.strato.de
set SMTP_PORT=587
set SMTP_USERNAME=beheer@alphen.cloud
set SMTP_PASSWORD=***
set SMTP_FROM=beheer@alphen.cloud
set SMTP_STARTTLS=1
set SMTP_DEBUG=1
REM Option B: put the same keys/values in a .env file instead
Security note: do not commit SMTP passwords to the repo. Prefer secrets management and rotate leaked credentials.
Note on the "From" address: some SMTP providers enforce that the authenticated mailbox
(SMTP_USERNAME) is used as the actual sender (envelope-from), even if a different
SMTP_FROM is provided. In that case the app sets a Reply-To header so replies still
go to SMTP_FROM, but the provider may still show the username address as the sender.
Troubleshooting mail delivery
If the reset email is not received:
- Set
SMTP_DEBUG=1and request a reset again. - Watch the Flask console output for SMTP responses / errors.
- Verify:
SMTP_USERNAMEandSMTP_FROMare allowed by your provider.- You are using STARTTLS (port 587).
- The recipient mailbox isn’t filtering it (spam/quarantine).