A lightweight, self-hosted, plugin-extensible digital signage server. Point any browser at a URL and it becomes a screen — no agents to install, no cloud account, no per-display licensing.
StreetSign is a self-contained web server for running digital signage. You create and schedule posts (text, images, video, web pages, and more), organise them into feeds, and arrange those feeds into zones on configurable *screen layouts.
Each physical display is just a browser — a PC, a Raspberry Pi, a smart TV, tablet, etc. pointed at a URL. The display client loads its layout and continuously polls the server for new content. Admins author and publish everything from a web control panel; the server handles scheduling, permissions, and housekeeping.
There are plenty of digital signage projects. StreetSign is built around a few deliberate choices that set it apart:
-
Genuinely lightweight. SQLite is the only datastore, and static assets are served in-process by WhiteNoise
-
Nothing to install on the screens. Display clients are ordinary web browsers. A dedicated
notransrendering engine (usingrequestAnimationFrame) targets low-powered devices, and a separatemobileengine handles phones and tablets. -
An editorial workflow. Permissions are granted per-feed at three levels — read, write, and publish — to individual users or groups. Authoring and publishing are deliberately separate, so contributors can draft content while only trusted users push it live.
-
Extensible by dropping in a folder. Both post types and external content importers are plugin systems: StreetSign auto-discovers any module under
streetsign_server/post_types/orstreetsign_server/external_source_types/. Post content is stored as JSON, so new post types need no database migration. -
Automation-friendly. A web hook post type fires HTTP requests on render, display, and hide — ideal for driving external stream players (e.g. VLC) or home/venue automation. RSS/Atom feeds and local image folders can be auto-imported on a schedule, with a preview before you commit.
-
Free, self-hosted, GPLv3. Your content and database stay on your hardware. No SaaS subscription, no per-screen fees, no phone-home.
StreetSign grew out of running signage for large conferences, and it's well suited to anywhere you need flexible, multi-screen displays under your own control:
- Conferences & events — different layouts per hall or stage, switched per display via client aliases; scheduled session info and announcements.
- Churches & community spaces — rotating notices, event listings, and RSS-imported news.
- Offices & lobbies — dashboards, welcome messages, embedded web pages.
- Schools & campuses — timetables, alerts, and per-department feeds.
- Retail & hospitality — menu boards, promotions, looping video.
- Makerspaces & labs — status screens driven by web hooks and automation.
| Type | Description |
|---|---|
| Plain Text | Unformatted text, auto-scaled to fill the zone |
| Rich Text | Formatted content via the Quill WYSIWYG editor, sanitised with Bleach |
| Image | Uploaded or remote images, displayed with background-size: contain |
| Video | HTML5 video with loop — muted autoplay by default, tap to enable audio |
| External Web Page | Embeds any URL in a full-zone iframe |
| Web hook | POSTs to external URLs on render, display, and hide — for controlling stream players (e.g. VLC) or automation systems |
| Raw HTML | Arbitrary, unsanitised HTML rendered in a sandboxed iframe |
New post types can be added via the plugin system (streetsign_server/post_types/).
Display clients load one of three rendering engines, selected per client alias:
| Engine | Technology | Best for |
|---|---|---|
| basic | CSS3 transitions (opacity for fades, translateX for scroll) |
Modern browsers, full-featured PCs |
| notrans | JavaScript requestAnimationFrame for scroll |
Raspberry Pi, low-powered devices |
| mobile | Lightweight vanilla CSS | Phones and tablets |
- Post lifetime — start/end dates and times, or mark a post "Show permanently" (never expires, never rotates)
- Time-of-day restrictions — blackout windows or exclusive windows (e.g. "only show between 09:00 and 17:00")
- Display duration — how many seconds each post stays visible (2–100s)
- Per-post font size — override the automatic zone font scaling
Three permission levels per feed, assignable to users and groups:
- Read — view posts in the feed
- Write — create and edit posts
- Publish — mark posts ready for display (separate from write — the dashboard highlights unpublished posts)
Admins bypass all permission checks. Locked-out accounts are denied everything. Sessions are tracked server-side and validated on every request.
StreetSign is a single web server. Browsers acting as display clients load a screen layout, then continuously poll for posts from the feeds assigned to each zone. Admins create and publish posts through the web control panel, design multi-zone screen layouts, and optionally import content automatically from RSS feeds or local image folders.
Each screen layout is a set of rectangular zones positioned on a background.
Each zone subscribes to one or more feeds and cycles through their posts, using
either a fade (opacity cross-fade) or scroll (horizontal slide)
transition. Zones can be styled per-layout with custom CSS, background images,
user-uploaded fonts (.ttf/.otf), and per-zone font and colour overrides.
Client aliases map a short access key (like /client/mainhall) to a
specific screen + engine combination with display overrides (aspect ratio,
fade time, scroll speed). Different physical displays can use different layouts
without ever changing the bookmark on the client.
External content can be imported automatically:
- RSS / Atom feeds — each entry is rendered through a Jinja2 template, sanitised with Bleach, and saved as a Rich Text post (with deduplication).
- Local image folder — a server-side directory is watched for new images, each becoming an Image post.
Both importers run on a configurable schedule, can optionally auto-publish, and offer a test/preview button plus a manual "Run Now".
git clone https://github.com/jamswat/streetsign.git
cd streetsign
./setup.sh
./run.pyOpen http://localhost:5000 — default login is admin / admin.
Change the password immediately before deploying anywhere.
A fresh database is seeded with three demo accounts (password = login name):
admin (full admin), editor (write/publish on all feeds via the editors
group), and viewer (read-only). It also includes example feeds, posts, and a
ready-to-use two-zone Default screen at /screens/basic/Default.
A multi-stage Docker image is provided — the build compiles C extensions in a
builder stage and ships a slim final image (~45 MB) that runs as a non-root
streetsign user. Static assets are served in-process by WhiteNoise (no nginx
sidecar required), so the container can be exposed directly or placed behind
any reverse proxy.
docker build -t streetsign .
docker run -d --name streetsign -p 5000:5000 streetsignOpen http://localhost:5000 — default login is admin / admin.
docker compose up -dThis brings up a single app service on ${WEB_PORT:-5000}. Two named volumes
are created automatically and persist across rebuilds:
| Volume | Mount path | Purpose |
|---|---|---|
db_data |
/data |
SQLite database |
uploads |
/app/streetsign_server/static/user_files |
User-uploaded images, fonts, etc. |
Built-in static assets (main.js, style.css, lib/, screens/) are baked
into the image and are not mounted on a volume, so changes to them appear
on the next rebuild without stale-file masking. Only user_files/ (runtime
uploads) is persisted.
Mount volumes for the SQLite database and uploaded files, otherwise data is lost when the container is removed:
docker run -d -p 5000:5000 \
-v streetsign-db:/data \
-v streetsign-uploads:/app/streetsign_server/static/user_files \
streetsignOn first start (empty /data), the container seeds a fresh database with the
default admin user, feeds, and a sample screen. On subsequent starts it only
runs pending migrations.
| Variable | Default | Notes |
|---|---|---|
SECRET_KEY |
dev-default-key-change-in-production |
Flask session-signing key. Change this before deploying. Generate one with python3 -c "import uuid; print(uuid.uuid4())" and pass via -e SECRET_KEY=... or a .env file. |
WEB_PORT |
5000 |
Host port to publish (compose only) |
PORT |
5000 |
Port the server listens on inside the container |
HOST |
0.0.0.0 |
Bind address inside the container |
DATABASE_FILE |
/data/database.db |
SQLite path (already volume-mounted in image) |
Override at runtime, e.g. to serve on port 8080:
docker run -d -p 8080:8080 -e PORT=8080 streetsignOr with compose, publish on a different host port:
WEB_PORT=8080 docker compose up -dFor production you should mount your own config.py (see config_default.py
for the full list of options):
docker run -d -p 5000:5000 -v "$PWD/config.py:/app/config.py:ro" streetsign./run.py waitressThis runs the app under the Waitress WSGI server. Put it behind any reverse proxy you like (or expose it directly — WhiteNoise handles static files in-process).
StreetSign loads config.py if present, falling back to the defaults in
config_default.py. Don't edit config_default.py; instead copy the values
you want to change into config.py. Common environment variables
(SECRET_KEY, DATABASE_FILE, PORT, HOST) are honoured directly.
- Back up
database.dbandconfig.py git pullmake migrate- Restart the server
- Python 3.9+
- ImageMagick (for image resizing and thumbnails)
Debian/Ubuntu:
apt-get install python3-dev python3-pip imagemagickThe setup.sh script (which runs make all) creates a .virtualenv with all
Python dependencies. To use the virtualenv directly: .virtualenv/bin/python.
Tests run against an in-memory SQLite database, so they're fast and isolated:
.virtualenv/bin/python -m pytest tests/
.virtualenv/bin/python -m pylint streetsign_server/Full documentation at https://jamswat.github.io/streetsign/.
Why isn't my post showing up?
- Is it published?
- Does the screen have the correct feeds selected?
- Are time-of-day restrictions blocking it?
- Is it within its active lifetime (start/end dates)?
See the CHANGELOG for a summary of recent improvements.
StreetSign was originally written by Daniel Fairhead for Teenstreet 2013 in Germany (released under GPLv3), and used at large conferences and in corporate environments since. It was further developed by Daniel Lang (2020–2024). Both upstream projects now appear to be abandoned; this is a maintained fork that continues their work.
Code in this repository has been developed with assistance from AI coding tools, including the Bootstrap 3→5 migration, the Knockout.js→Alpine.js replacement, HTML/CSS/JS modernization, and dependency updates.
