Visualisation of real-time track data and telemetry synced to F1 replays.
TypeScript
916
154 commits
updated Oct 15, 2026
https://github.com/user-attachments/assets/5c62dcf8-3156-4079-96d1-bd24ce64ebda
A web app for watching Formula 1 sessions with real timing data, car positions on track, driver telemetry, and more - both live during race weekends and as replays of past sessions. Built with Next.js and FastAPI.
Everything runs as a single container on one port. The frontend and backend are the same service — no cross-origin configuration, no CORS, no separate URLs to manage.
Session data is processed once and stored locally (or in Cloudflare R2 for persistence). You can either pre-compute data in bulk ahead of time, or let the app process sessions on demand when you select them.
Requires Docker and Docker Compose.
1. Clone the repository:
git clone <repo-url>
cd F1timing
2. Configure:
cp .env.example .env
Edit .env — the defaults work out of the box. Change PORT if you want a different port.
3. Run:
docker compose up
Open http://localhost:8000. Select any past session and it will be processed on demand.
The app runs as a single container on one port — no URL configuration required.
All configuration is managed through the .env file. No need to edit docker-compose.yml
| Variable | Default | Description |
|---|---|---|
PORT | 8000 | Port the app runs on |
DATA_DIR | /data | Where session data is stored |
STORAGE_MODE | local | local stores data on disk, r2 reads from Cloudflare R2 |
R2_ACCOUNT_ID | Cloudflare R2 account ID (only for r2 mode) | |
R2_ACCESS_KEY_ID | Cloudflare R2 access key (only for r2 mode) | |
R2_SECRET_ACCESS_KEY | Cloudflare R2 secret key (only for r2 mode) | |
R2_BUCKET_NAME | f1timingdata | R2 bucket name |
AUTH_ENABLED | false | Enable passphrase authentication |
AUTH_PASSPHRASE | Required passphrase when auth is enabled | |
OPENROUTER_API_KEY | Enables photo sync feature (get a key) | |
AUTO_PRECOMPUTE | race+qual | Which sessions to fetch in the background on race weekends (see Session data) |
RETENTION_SWEEP_ENABLED | true | Set to false to stop this instance running the daily retention sweep, e.g. when several instances share one bucket |
REPLAY_PLAYER | static | static plays replays in the browser from small compressed files. websocket uses the previous player, which streams frames from the server |
The app serves everything (frontend, API, WebSocket) from a single port. Point your reverse proxy at the container port.
Traefik:
services:
f1timing:
image: ghcr.io/adn8naiagent/f1replaytiming:latest
env_file: .env
volumes:
- f1data:/data
- f1cache:/data/fastf1-cache
labels:
- traefik.enable=true
- traefik.http.routers.f1timing.rule=Host(`f1.example.com`)
- traefik.http.routers.f1timing.entrypoints=websecure
- traefik.http.routers.f1timing.tls.certresolver=letsencrypt
- traefik.http.services.f1timing.loadbalancer.server.port=8000
nginx:
server {
server_name f1.example.com;
location / {
proxy_pass http://localhost:8000;
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;
}
}
Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:8000
Key points:
NEXT_PUBLIC_API_URL or FRONTEND_URL variables to setSet PORT in your .env if needed, then access via your machine's IP:
http://192.168.1.50:8000
No other configuration required. The app uses relative URLs, so it works on any address.
You can also use pre-built images instead of building from source:
# Pull and run directly
docker run -d -p 8000:8000 --env-file .env -v f1data:/data -v f1cache:/data/fastf1-cache ghcr.io/adn8naiagent/f1replaytiming:latest
# On a different port, pass PORT and map the same port on both sides
docker run -d -e PORT=9000 -p 9000:9000 --env-file .env -v f1data:/data -v f1cache:/data/fastf1-cache ghcr.io/adn8naiagent/f1replaytiming:latest
Or with docker-compose:
services:
f1timing:
image: ghcr.io/adn8naiagent/f1replaytiming:latest
ports:
- "${PORT:-8000}:${PORT:-8000}"
env_file: .env
environment:
PORT: ${PORT:-8000}
volumes:
- f1data:/data
- f1cache:/data/fastf1-cache
volumes:
f1data:
f1cache:
Deploy as a single service. Most platforms will detect the Dockerfile at the project root automatically.
Set your environment variables in the platform's dashboard. R2 credentials are recommended since most cloud platforms have ephemeral filesystems, so locally computed session data would be lost on redeploy.
Session data is persisted in a Docker volume, so it survives restarts.
On-demand processing: Simply select any past session from the homepage. If the data hasn't been processed yet, the app will automatically fetch and process it. The first load of a session takes 1-3 minutes. After that, it's instant.
Bulk pre-compute: Use the CLI script to process sessions ahead of time:
# Process a specific race weekend
docker compose exec f1timing python precompute.py 2026 --round 1
# Process only the race session (skip practice/qualifying)
docker compose exec f1timing python precompute.py 2026 --round 1 --session R
# Process an entire season (will take several hours)
docker compose exec f1timing python precompute.py 2025 --skip-existing
# Process multiple years
docker compose exec f1timing python precompute.py 2024 2025 --skip-existing
Replay files from older versions: Sessions processed before 2.2 are converted to the new replay files automatically the first time they're opened (a few seconds per session). To convert everything in advance instead:
docker compose exec f1timing python precompute.py 2024 2025 2026 --chunks-only
Re-computing a session: Some updates (noted in the changelog as "requires re-compute") add new data to existing sessions. Re-run the same command without --skip-existing and it overwrites the stored session — that is the re-compute:
docker compose exec f1timing python precompute.py 2026 --round 1 --session R
This reprocesses from FastF1's local cache where available, so it usually does not re-download. To force a fresh pull from FastF1 (e.g. to pick up data F1 has since corrected), clear that session from FASTF1_CACHE_DIR first.
Timing estimates:
Background auto-precompute: On race weekends (Fri–Mon), a background task checks every 30 minutes for new session data and processes it ahead of time so the first click is instant. Which session types it fetches is controlled by the AUTO_PRECOMPUTE env var:
| Value | What it fetches |
|---|---|
off | Nothing — everything is fetched on-demand the first time you click it |
race | Race + sprint |
race+qual | Race, sprint, qualifying, sprint qualifying — default |
all | Every session including practice |
Sessions outside the configured set are still available — they're just processed on demand (with the usual 1–3 minute first-load wait) rather than pre-fetched.
git clone <repo-url>
cd F1timing
cp .env.example .env
# Edit .env with your settings
cd frontend
npm install
npm run build
cd ..
This produces static files in frontend/out/.
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
STATIC_DIR=../frontend/out uvicorn main:app --host 0.0.0.0 --port 8000
Open http://localhost:8000.
The broadcast sync feature lets you match the replay to a recording of a session. You can always sync manually by entering gap times directly. To also enable photo/screenshot sync (where the app reads the timing tower from an image), set an OpenRouter API key as OPENROUTER_API_KEY. It uses a vision model (Gemini Flash) to read the leaderboard from the photo. Any OpenRouter-compatible API key will work.
This project is powered by FastF1, an open-source Python library for accessing Formula 1 timing and telemetry data. FastF1 is the original inspiration and data source for this project - without it, none of this would be possible.
Disclaimer: This project is non-commercial. This website is unofficial and is not associated in any way with the Formula 1 companies. F1, FORMULA ONE, FORMULA 1, FIA FORMULA ONE WORLD CHAMPIONSHIP, GRAND PRIX and related marks are trade marks of Formula One Licensing B.V.
197 followers · starred Mar 2026
850 followers · starred Mar 2026
75 followers · starred Mar 2026
64 followers · starred Mar 2026
Visualisation of real-time track data and telemetry synced to F1 replays.
TypeScript
916
154 commits
updated Oct 15, 2026
https://github.com/user-attachments/assets/5c62dcf8-3156-4079-96d1-bd24ce64ebda
A web app for watching Formula 1 sessions with real timing data, car positions on track, driver telemetry, and more - both live during race weekends and as replays of past sessions. Built with Next.js and FastAPI.
Everything runs as a single container on one port. The frontend and backend are the same service — no cross-origin configuration, no CORS, no separate URLs to manage.
Session data is processed once and stored locally (or in Cloudflare R2 for persistence). You can either pre-compute data in bulk ahead of time, or let the app process sessions on demand when you select them.
Requires Docker and Docker Compose.
1. Clone the repository:
git clone <repo-url>
cd F1timing
2. Configure:
cp .env.example .env
Edit .env — the defaults work out of the box. Change PORT if you want a different port.
3. Run:
docker compose up
Open http://localhost:8000. Select any past session and it will be processed on demand.
The app runs as a single container on one port — no URL configuration required.
All configuration is managed through the .env file. No need to edit docker-compose.yml
| Variable | Default | Description |
|---|---|---|
PORT | 8000 | Port the app runs on |
DATA_DIR | /data | Where session data is stored |
STORAGE_MODE | local | local stores data on disk, r2 reads from Cloudflare R2 |
R2_ACCOUNT_ID | Cloudflare R2 account ID (only for r2 mode) | |
R2_ACCESS_KEY_ID | Cloudflare R2 access key (only for r2 mode) | |
R2_SECRET_ACCESS_KEY | Cloudflare R2 secret key (only for r2 mode) | |
R2_BUCKET_NAME | f1timingdata | R2 bucket name |
AUTH_ENABLED | false | Enable passphrase authentication |
AUTH_PASSPHRASE | Required passphrase when auth is enabled | |
OPENROUTER_API_KEY | Enables photo sync feature (get a key) | |
AUTO_PRECOMPUTE | race+qual | Which sessions to fetch in the background on race weekends (see Session data) |
RETENTION_SWEEP_ENABLED | true | Set to false to stop this instance running the daily retention sweep, e.g. when several instances share one bucket |
REPLAY_PLAYER | static | static plays replays in the browser from small compressed files. websocket uses the previous player, which streams frames from the server |
The app serves everything (frontend, API, WebSocket) from a single port. Point your reverse proxy at the container port.
Traefik:
services:
f1timing:
image: ghcr.io/adn8naiagent/f1replaytiming:latest
env_file: .env
volumes:
- f1data:/data
- f1cache:/data/fastf1-cache
labels:
- traefik.enable=true
- traefik.http.routers.f1timing.rule=Host(`f1.example.com`)
- traefik.http.routers.f1timing.entrypoints=websecure
- traefik.http.routers.f1timing.tls.certresolver=letsencrypt
- traefik.http.services.f1timing.loadbalancer.server.port=8000
nginx:
server {
server_name f1.example.com;
location / {
proxy_pass http://localhost:8000;
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;
}
}
Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:8000
Key points:
NEXT_PUBLIC_API_URL or FRONTEND_URL variables to setSet PORT in your .env if needed, then access via your machine's IP:
http://192.168.1.50:8000
No other configuration required. The app uses relative URLs, so it works on any address.
You can also use pre-built images instead of building from source:
# Pull and run directly
docker run -d -p 8000:8000 --env-file .env -v f1data:/data -v f1cache:/data/fastf1-cache ghcr.io/adn8naiagent/f1replaytiming:latest
# On a different port, pass PORT and map the same port on both sides
docker run -d -e PORT=9000 -p 9000:9000 --env-file .env -v f1data:/data -v f1cache:/data/fastf1-cache ghcr.io/adn8naiagent/f1replaytiming:latest
Or with docker-compose:
services:
f1timing:
image: ghcr.io/adn8naiagent/f1replaytiming:latest
ports:
- "${PORT:-8000}:${PORT:-8000}"
env_file: .env
environment:
PORT: ${PORT:-8000}
volumes:
- f1data:/data
- f1cache:/data/fastf1-cache
volumes:
f1data:
f1cache:
Deploy as a single service. Most platforms will detect the Dockerfile at the project root automatically.
Set your environment variables in the platform's dashboard. R2 credentials are recommended since most cloud platforms have ephemeral filesystems, so locally computed session data would be lost on redeploy.
Session data is persisted in a Docker volume, so it survives restarts.
On-demand processing: Simply select any past session from the homepage. If the data hasn't been processed yet, the app will automatically fetch and process it. The first load of a session takes 1-3 minutes. After that, it's instant.
Bulk pre-compute: Use the CLI script to process sessions ahead of time:
# Process a specific race weekend
docker compose exec f1timing python precompute.py 2026 --round 1
# Process only the race session (skip practice/qualifying)
docker compose exec f1timing python precompute.py 2026 --round 1 --session R
# Process an entire season (will take several hours)
docker compose exec f1timing python precompute.py 2025 --skip-existing
# Process multiple years
docker compose exec f1timing python precompute.py 2024 2025 --skip-existing
Replay files from older versions: Sessions processed before 2.2 are converted to the new replay files automatically the first time they're opened (a few seconds per session). To convert everything in advance instead:
docker compose exec f1timing python precompute.py 2024 2025 2026 --chunks-only
Re-computing a session: Some updates (noted in the changelog as "requires re-compute") add new data to existing sessions. Re-run the same command without --skip-existing and it overwrites the stored session — that is the re-compute:
docker compose exec f1timing python precompute.py 2026 --round 1 --session R
This reprocesses from FastF1's local cache where available, so it usually does not re-download. To force a fresh pull from FastF1 (e.g. to pick up data F1 has since corrected), clear that session from FASTF1_CACHE_DIR first.
Timing estimates:
Background auto-precompute: On race weekends (Fri–Mon), a background task checks every 30 minutes for new session data and processes it ahead of time so the first click is instant. Which session types it fetches is controlled by the AUTO_PRECOMPUTE env var:
| Value | What it fetches |
|---|---|
off | Nothing — everything is fetched on-demand the first time you click it |
race | Race + sprint |
race+qual | Race, sprint, qualifying, sprint qualifying — default |
all | Every session including practice |
Sessions outside the configured set are still available — they're just processed on demand (with the usual 1–3 minute first-load wait) rather than pre-fetched.
git clone <repo-url>
cd F1timing
cp .env.example .env
# Edit .env with your settings
cd frontend
npm install
npm run build
cd ..
This produces static files in frontend/out/.
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
STATIC_DIR=../frontend/out uvicorn main:app --host 0.0.0.0 --port 8000
Open http://localhost:8000.
The broadcast sync feature lets you match the replay to a recording of a session. You can always sync manually by entering gap times directly. To also enable photo/screenshot sync (where the app reads the timing tower from an image), set an OpenRouter API key as OPENROUTER_API_KEY. It uses a vision model (Gemini Flash) to read the leaderboard from the photo. Any OpenRouter-compatible API key will work.
This project is powered by FastF1, an open-source Python library for accessing Formula 1 timing and telemetry data. FastF1 is the original inspiration and data source for this project - without it, none of this would be possible.
Disclaimer: This project is non-commercial. This website is unofficial and is not associated in any way with the Formula 1 companies. F1, FORMULA ONE, FORMULA 1, FIA FORMULA ONE WORLD CHAMPIONSHIP, GRAND PRIX and related marks are trade marks of Formula One Licensing B.V.
197 followers · starred Mar 2026
850 followers · starred Mar 2026
75 followers · starred Mar 2026
64 followers · starred Mar 2026