Every racing series in one place, and the drivers you follow, wherever they race.
Lights Out is a small self-hosted web app for motorsport fans. It puts the schedules of F1, F2, F3, F1 Academy, Formula E, IndyCar, NASCAR (Cup, O'Reilly, Truck), WEC, WRC, GT World Challenge Europe, the Nürburgring NLS, rallycross and more into one calendar. Follow a driver by name and it watches every grid and entry list for them. That matters when a driver hops between series: Max Verstappen turning up at a GT race, or a young driver doing rallies one month and rally-raids the next.
It installs on a phone like an app (no app store), sends alerts, and you host it yourself for you and your friends.
A passion project, vibecoded. Lights Out was built by one fan, entirely by talking it through with Claude Code, Anthropic's AI coding assistant. The fan described what they wanted, Claude wrote the code, and real use on race weekends shaped what came next. It's tested (
pytest) and runs every day, but it's a hobby project, not a product. Expect rough edges, and feel free to fix them, with or without an AI helping you.CLAUDE.mdholds the notes Claude works from, so you can pick up where it left off.
- Upcoming: every session for the series you switch on, in your time zone, with US where-to-watch badges that make FREE vs PAID obvious.
- Follows: follow drivers or teams by name (spelling doesn't need to be perfect). A NEW badge appears when they're entered somewhere. Driver pages show where they race, their next sessions, championship positions and news.
- Standings and results for F1, F2, F3, F1 Academy, Formula E, IndyCar, WRC (and its classes) and NASCAR.
- Weather at the track for sessions in the next 7 days (forecasts from Open-Meteo).
- Live timing for F1, F2, F3, F1 Academy, WRC, NASCAR and IndyCar, connected only while a session is on.
- Race events: penalties, safety cars, cautions with their cause, retirements and lead changes as they happen, plus a lap-by-lap Race story afterwards.
- Race Wire: racing news (Motorsport.com, Autosport, RACER, The Race, DirtFish and more) plus the big race moments, filterable by series, team or driver.
- Alerts (web push): race starts, cautions, your drivers' incidents and results, news about them.
- Calendar feed: subscribe once in Google/Apple/Outlook calendar and it stays updated.
- Ticket links, a What's new page, an offline page when the server is down, and an approval list so only people you approve get in.
Lights Out is an unofficial fan project. It is not affiliated with or endorsed by Formula 1, the FIA, NASCAR, IndyCar, the ACO, WRC, SRO, the NLS or any team, broadcaster or news site. It reads the same public pages and feeds their own websites use, politely (most sources once a day, hourly in race weeks). Those feeds can change or disappear at any time. It shows no league or team logos. News items link to the original articles. Please don't run a public instance for strangers. Run it for yourself and your friends.
| A computer that's always on | Anything that runs Docker: a Raspberry Pi 4 or 5, a mini PC, a NAS, an old laptop, a small cloud server. Images are built for 64-bit ARM (Raspberry Pi OS 64-bit) and regular 64-bit PCs (x86-64). |
| How much power | Very little. On a Raspberry Pi 5 with 8 GB, Lights Out uses about 110 MB of RAM, well under 1% CPU, and its data is a few MB. A 2 GB Pi is plenty, and it happily shares the box with Jellyfin, Home Assistant and so on. |
| Docker | Docker Engine with the Compose plugin (docker compose). On a Pi: curl -fsSL https://get.docker.com | sh. |
| To use it away from home (optional) | A domain name (around $10/year) using Cloudflare for DNS, and a free Cloudflare account. Cloudflare Tunnel gets visitors to your box without opening ports on your router, and Cloudflare Access handles sign-in. |
| Approval pushes (optional) | The free ntfy app on your phone, so "Approve / Deny" arrives as a notification. |
Good for trying it out, or if only people on your Wi-Fi will use it. There's no sign-in, so never expose this mode to the internet.
mkdir lightsout && cd lightsout
curl -O https://raw.githubusercontent.com/DieLaughing/lightsout/main/compose.yaml
curl -o .env https://raw.githubusercontent.com/DieLaughing/lightsout/main/.env.exampleEdit .env: set LIGHTSOUT_ADMIN_EMAIL and LIGHTSOUT_LOCAL_EMAIL to your email, LIGHTSOUT_BASE_URL to
http://<that computer's IP>:8787, and TZ to your time zone.
To reach it from other devices on your network, change the port line in compose.yaml from "127.0.0.1:8787:8787"
to "8787:8787". Then:
docker compose up -dOpen http://<that computer's IP>:8787. The first load takes a minute while it fetches every series. Phone alerts need
HTTPS, so for those use the Cloudflare setup below.
This is how the original runs: people sign in with their email (a one-time code, no passwords), you approve who gets
in, and nothing on your home network is opened up. Below, replace racing.example.com with your own address.
1. Put your domain on Cloudflare. Create a free account at cloudflare.com, add your domain, and switch your registrar's nameservers to the two Cloudflare gives you. Wait until it says "Active".
2. Turn on Zero Trust. In the dashboard open Zero Trust. Pick a team name (it becomes
<team>.cloudflareaccess.com) and choose the Free plan (up to 50 users). Cloudflare may ask for a card even on
the free plan.
3. Create a tunnel. Zero Trust → Networks → Tunnels → Create a tunnel → Cloudflared. Name it, then copy the long
token from the install command (the part after --token). Put it in .env as TUNNEL_TOKEN=. You don't need to run
their install command, because the cloudflared service in compose.yaml does that job.
Then add a Public hostname to the tunnel:
- Subdomain + domain:
racing.example.com - Service:
HTTP, URLlightsout:8787(the app's name inside Docker Compose)
4. Lock it behind Cloudflare Access. Zero Trust → Access → Applications → Add an application → Self-hosted.
- Application domain:
racing.example.com(no path). Session duration: up to 1 month, so people rarely re-sign in. - Policy: Allow, Include Everyone, login method One-time PIN. "Everyone" can sign in, but Lights Out keeps its own approval list, so only people you approve get past its "Request access" screen. (Or list your friends' emails here instead and approve them in the app too.)
- Save, then open the application's overview and copy the Application Audience (AUD) Tag.
In .env set:
LIGHTSOUT_BASE_URL=https://racing.example.com
LIGHTSOUT_ADMIN_EMAIL=you@example.com # the email you'll sign in with
LIGHTSOUT_ACCESS_TEAM=<team>.cloudflareaccess.com
LIGHTSOUT_ACCESS_AUD=<the AUD tag>
LIGHTSOUT_LOCAL_EMAIL= # must stay empty on the internet5. Let a few paths skip sign-in. Some things can't sign in: calendar apps, phones fetching home-screen icons, and
the Approve/Deny buttons in the ntfy app. Add a second self-hosted application (call it lightsout-public-paths)
with a Bypass policy (Include: Everyone) and these destinations:
| Path | Why it's safe |
|---|---|
racing.example.com/cal |
Calendar feeds: protected by a long secret token in each link |
racing.example.com/act |
ntfy Approve/Deny buttons: single-use signed links that expire in 24 h |
racing.example.com/icons |
App icons |
racing.example.com/manifest.webmanifest |
The "install as app" description |
racing.example.com/apple-touch-icon.png |
iPhone home-screen icon |
racing.example.com/apple-touch-icon-precomposed.png |
Older iPhone icon lookup |
6. Start it.
docker compose --profile tunnel up -dVisit https://racing.example.com, sign in with your admin email, and you're in. Friends sign in with theirs, tap
Request access, and you approve them from the Admin tab, or straight from your phone (step 7). Admin can also
make invite links that let someone in without the request step.
7. (Optional) Approve from your phone with ntfy. Install the ntfy app and subscribe to a topic. On the public
server ntfy.sh, pick a long random topic name, since anyone who knows it can read the requests. Then set
NTFY_URL=https://ntfy.sh and LIGHTSOUT_NTFY_TOPIC=<your topic> in .env. If you self-host ntfy with access control,
also set NTFY_TOKEN. Run docker compose up -d again to apply.
Lights Out signs people in with Cloudflare Access only (it checks Cloudflare's signed token). Without Cloudflare, use home-network mode behind your own VPN (Tailscale, WireGuard). Support for other sign-in providers would be a welcome contribution.
Open the site in Safari (iPhone) or Chrome (Android) and go to Settings → Get the app. It walks you through adding it to the home screen and turning on alerts. On iPhone, alerts only work from the home-screen app.
Everything is in .env. .env.example explains each line.
| Setting | What it does |
|---|---|
LIGHTSOUT_BASE_URL |
Your site's address, used in calendar and approval links |
LIGHTSOUT_ADMIN_EMAIL |
The admin: always let in, sees the Admin tab |
LIGHTSOUT_ACCESS_TEAM, LIGHTSOUT_ACCESS_AUD |
Cloudflare Access team domain and the app's AUD tag |
LIGHTSOUT_LOCAL_EMAIL |
Home-network mode: no sign-in, everyone is this person |
NTFY_URL, NTFY_TOKEN, LIGHTSOUT_NTFY_TOPIC |
Approval pushes (optional) |
LIGHTSOUT_GITHUB_TOKEN, LIGHTSOUT_GITHUB_REPO |
In-app bug reports filed as GitHub issues (optional; see .env.example) |
TUNNEL_TOKEN |
Cloudflare Tunnel token (with --profile tunnel) |
TZ |
Server time zone (logs) |
What friends choose (series, follows, alerts, calendar) is saved per person in the app.
The name, colors and icon are easy to change:
- Name: "Lights Out" appears in
web/index.html(title andapple-mobile-web-app-title),web/manifest.webmanifest(name,short_name),web/offline.html,web/sw.js(notification title),web/app.js(a few sentences),lightsout/access.pyandlightsout/app.py(push and ntfy messages),lightsout/ics.py(calendar name) andtools/changelog.py. To find them all:grep -rn "Lights Out" web lightsout tools. - Colors: the CSS variables at the top of
web/style.css(--accentand friends, light and dark), plustheme_color/background_colorin the manifest. - Icon: replace
web/icons/icon.svg, then make the PNGs from it, for examplersvg-convert -w 512 icon.svg > icon-512.png(also 192 and 180). Bump the?v=number on the icon links inweb/index.htmland the manifest so phones fetch the new one. - Source link: if you run a modified copy for other people, point
<meta name="source">inweb/index.htmlat your fork (see License). - Where-to-watch is for the US.
lightsout/data/broadcasts.yamlholds the rules if you want to adapt it.
After changing code, build your own image: in compose.yaml replace the image: line with build: . and run
docker compose up -d --build.
docker compose pull && docker compose up -d # new version (published with each GitHub Release)Everything that matters is in the data/ folder next to compose.yaml: the database, your approved list, the
calendar token and the push-notification key. Back that folder up. If you lose the push key, everyone has to turn
alerts on again.
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt pytest httpx
LIGHTSOUT_LOCAL_EMAIL=me@example.com LIGHTSOUT_ADMIN_EMAIL=me@example.com .venv/bin/uvicorn lightsout.app:app --port 8787
.venv/bin/python -m pytest -qData goes to ./data. LIGHTSOUT_NO_REFRESH=1 serves existing data without fetching. There's no build step: the web
app is plain HTML/CSS/JS in web/.
| Path | What |
|---|---|
lightsout/adapters/ |
One module per source; each returns events and sessions in a common shape |
lightsout/live/ |
Live timing feeds and the race-event classifier |
lightsout/data/*.yaml |
Hand-kept data: broadcasts, tickets, team aliases, manual schedules, What's new |
lightsout/refresh.py |
Refreshes each series daily, hourly in race weeks |
web/ |
The installable web app |
CLAUDE.md |
How everything works, written for Claude Code (and humans) |
Contributions are welcome: new series, fixes when a source changes its format, other countries' where-to-watch, other sign-in providers. See CONTRIBUTING.md. In short: fork, make your change on a branch, run the tests, and open a pull request.
GNU AGPL v3.0 or later. In plain words: use it, change it, share it. If you run a modified version that other people use over the internet, you have to offer them your source code (the "Source code" link on the What's new page does that; point it at your fork). That way, improvements find their way back to everyone. Running it unmodified for you and your friends needs nothing extra.