You pay for a 500 Mbps or 1 Gbps connection, and most days it feels fine. Then one evening Netflix starts buffering, your video calls turn to mush, and you're left wondering if it's your ISP, your router, or something on your own network. Without data, you're just guessing — and guessing usually means an hour on hold with your ISP's support line, describing a problem that already went away by the time someone picks up.
Speedtest Tracker fixes that by quietly running speed tests on a schedule and keeping a history you can point to. This guide walks through getting it running in its own Proxmox VE LXC container, using Docker inside that container, since that's how the app is actually packaged and supported upstream.
What You Will Learn
By the time you're done, you'll have a dedicated container running Speedtest Tracker around the clock, logging results you can graph and export. Specifically, you'll:
- Create a Debian 13 LXC container sized correctly for a small Docker workload
- Enable the Proxmox VE container feature that lets Docker run inside an unprivileged LXC
- Install Docker and Docker Compose inside the container
- Deploy Speedtest Tracker with a docker-compose.yml file and the environment variables it actually needs
- Set an automatic test schedule and fix the errors people run into most often
What Is Speedtest Tracker?
Speedtest Tracker is an open-source, self-hosted app that runs Ookla speed tests — the same engine behind speedtest.net — on a timer, then stores every result in a database so you can look back over days, weeks, or months. It shows download and upload throughput, latency, jitter, and packet loss on a dashboard with graphs, instead of a single number that disappears the moment you close the browser tab.
It ships as a container image maintained by LinuxServer.io, built on PHP and Laravel under the hood, with SQLite as its default database. There's no separate database server to stand up unless you want one.
Why Would You Use It?
A one-off speed test tells you how your connection is doing right now. It says nothing about 3 a.m. last Tuesday, or whether your ISP quietly started throttling you after a "network maintenance" email. Speedtest Tracker's whole value is the history: when a household member complains the internet is slow, you can pull up a graph instead of relying on memory.
It's also genuinely useful evidence. ISPs are far more responsive to a support ticket that includes a week of logged results showing your speed dropped from 480 Mbps to 60 Mbps every evening than to "it feels slow sometimes." A few homelabbers have gotten refunds or truck rolls this way.
Running it in its own Proxmox VE LXC container keeps it isolated from everything else on your host. If you break something experimenting with the config, you roll back a snapshot instead of untangling it from other services sharing the same box.
Prerequisites
Before you start, make sure you have:
- Proxmox VE 8.x or 9.x installed and reachable through the web interface — this guide was written and tested against 9.2
- A Debian 13 "Trixie" LXC template available on your storage (you'll download it below if you don't have it)
- At least 8 GB of free storage and 2 GB of free RAM on the host to allocate to the new container
- Basic comfort with a Linux shell — copy-pasting commands and reading error output is enough
- Network access from your workstation to both the Proxmox host and the new container's IP address
You don't need a domain name, a reverse proxy, or SSL for this. We'll reach the dashboard over plain HTTP on your LAN first, and you can put it behind something like Nginx Proxy Manager later if you want it reachable from outside your network.
Step-by-Step Tutorial
Step 1: Download the Debian 13 template
In the Proxmox web interface, click your node, then go to local (Storage) > CT Templates > Templates. Search for "debian-13" and download the standard template — it's around 130 MB.
Prefer the shell? SSH into your Proxmox host and run:
pveam update
pveam available | grep debian-13
pveam download local debian-13-standard_13.1-1_amd64.tar.zst
The exact filename shifts as Proxmox refreshes its template list, so check the search output before you copy the download command blindly.
Step 2: Create the container
Click Create CT in the top-right corner. Work through the wizard with these settings:
- General: hostname like
speedtest-tracker, set a root password, leave Unprivileged container checked - Template: the debian-13-standard image you just downloaded
- Disk: 8 GB — Docker's own image layers and logs add up faster than the app itself does
- CPU: 2 cores. The app is idle most of the time, but a speed test briefly pushes CPU while it saturates your connection
- Memory: 2048 MB. PHP-FPM plus Docker's daemon overhead wants more headroom than a bare Node.js app would
- Network: bridge to
vmbr0(or whatever bridge your other containers use), DHCP unless you run static IPs - DNS: leave the defaults unless your network has a specific DNS server containers should use
Click through to Confirm and Finish, then select the container and click Start. Don't start Docker's install script yet — there's one setting to change first.
Step 3: Enable nesting so Docker can actually run
Proxmox VE's LXC containers share the host's kernel instead of virtualizing their own, which is exactly what makes them fast and lightweight. It's also why Docker won't run inside one by default: Docker wants to create its own nested containers and cgroups, and an unprivileged LXC blocks that unless you explicitly allow it.
With the container stopped, go to the container's Options tab, double-click Features, and check Nesting. If you're on ZFS or a newer kernel and Docker still complains about permissions later, come back here and enable keyctl too — most setups only need nesting.
You can do the same thing from the host shell instead of the GUI:
pct set 105 --features nesting=1
Swap 105 for your container's actual ID. Start the container again once this is set.
Step 4: Update the container and install Docker
Open the container's console — click >_ Console in the web UI, or run pct enter 105 from the host shell. Update the package lists first:
apt update && apt upgrade -y
apt install -y curl ca-certificates
Then install Docker using the official convenience script, which handles the repository setup and package installation for you:
curl -fsSL https://get.docker.com | sh
This takes a couple of minutes on most connections. Once it finishes, confirm Docker is actually running and that the Compose plugin came along with it:
docker --version
docker compose version
Both commands should print a version number. If docker compose version comes back as "command not found," the convenience script didn't install the plugin — reinstall with apt install -y docker-compose-plugin and try again.
Step 5: Generate an application key
Speedtest Tracker needs a unique encryption key to protect data it stores, similar to how Laravel apps in general handle this. Generate one with OpenSSL, which is already installed on Debian by default:
echo "base64:$(openssl rand -base64 32)"
Copy the full output, including the base64: prefix — you'll paste it into the compose file in the next step. Generate your own; don't reuse an example key from a tutorial, since that defeats the point of having one.
Step 6: Write the docker-compose.yml file
Create a working directory and the compose file:
mkdir -p /opt/speedtest-tracker
cd /opt/speedtest-tracker
nano docker-compose.yml
Paste in the following, replacing APP_KEY with the value you generated and APP_URL with your container's IP address:
services:
speedtest-tracker:
image: lscr.io/linuxserver/speedtest-tracker:latest
container_name: speedtest-tracker
restart: unless-stopped
ports:
- 8080:80
- 8443:443
environment:
- PUID=1000
- PGID=1000
- APP_KEY=base64:PASTE_YOUR_GENERATED_KEY_HERE
- APP_URL=http://192.168.1.50:8080
- APP_TIMEZONE=America/New_York
- DISPLAY_TIMEZONE=America/New_York
- DB_CONNECTION=sqlite
- SPEEDTEST_SCHEDULE=0 */2 * * *
volumes:
- ./data:/config
PUID and PGID tell the container which Linux user and group ID to run its processes as — 1000 is the default first non-root user on most Debian systems, which keeps file ownership on the mounted volume sane. Set your own timezones so the graphs and schedule line up with your actual clock instead of UTC.
Step 7: Start the container and check it came up clean
docker compose up -d
docker compose logs -f
Watch the logs for a minute. You're looking for the container settling into a steady state without repeating errors — some database migration output near the start is normal. Press Ctrl+C to stop following the logs once it looks quiet.
Step 8: Log in and change the default password
From any device on your LAN, open:
http://<container-ip>:8080
Log in with the default credentials — email admin@example.com, password password — and change both immediately from the user settings menu. Leaving the default password in place on a device sitting on your home network isn't a huge risk, but there's no reason to leave that door unlocked either.
Once you're in, the dashboard will prompt you to run your first speed test if none has completed yet. Click through it once manually to confirm everything actually works before you trust the schedule to handle the rest.
Commands Explained
| Command | What it does |
|---|---|
pveam update | Refreshes the list of LXC templates Proxmox can download from its repositories |
pct set 105 --features nesting=1 | Enables the LXC feature flag that allows Docker's nested containers to run inside container 105 |
pct enter 105 | Opens a root shell directly inside container 105 from the Proxmox host, no SSH required |
curl -fsSL https://get.docker.com | sh | Downloads and runs Docker's official install script, which adds the correct repository for your distro and installs Docker Engine plus the Compose plugin |
docker compose up -d | Reads docker-compose.yml, pulls the image if needed, and starts the container in the background (detached mode) |
docker compose logs -f | Streams the container's log output live, so you can watch startup for errors |
docker exec -it speedtest-tracker php artisan app:user-reset-password | Runs Speedtest Tracker's built-in command to reset a user's password from inside the running container |
Common Errors
"docker: permission denied while trying to connect to the Docker daemon socket." You're running the command as a non-root user inside the container without sudo access to the Docker group. Either prefix your commands with sudo, or add your user to the docker group with usermod -aG docker $USER and log back in.
Container exits immediately after docker compose up -d with no obvious error in a quick glance. Run docker compose logs without the -f flag to see the full startup output instead of just the tail. Nine times out of ten it's a missing or malformed APP_KEY — the app refuses to start without one.
"Failed to start container: OCI runtime create failed... operation not permitted" when running docker compose up. This means nesting isn't actually enabled, or the container needs a restart after you changed the feature flag. Stop the container fully from the Proxmox host, confirm Nesting is checked under Options > Features, and start it again.
Dashboard loads but every speed test fails or times out. Usually the container can't reach the internet directly. Check that outbound traffic isn't blocked by the Proxmox firewall on this container, and confirm DNS resolution works inside the container with getent hosts speedtest.net.
Troubleshooting
Start with the container's own logs before guessing at anything else:
docker compose logs --tail=100
If the web UI won't load at all, confirm the container process is actually up:
docker compose ps
A status of "Up" with a health check listed as "healthy" means the app thinks it's fine, and the problem is more likely networking — check the Proxmox firewall rules under Datacenter > Firewall and the container's own Firewall tab.
Locked out of the dashboard because you forgot the password you set? Reset it from the shell without touching the database directly:
docker exec -it speedtest-tracker php artisan app:user-reset-password
If scheduled tests aren't running on the cadence you expect, double check the SPEEDTEST_SCHEDULE value in your compose file. It takes a standard cron expression — five fields for minute, hour, day of month, month, and day of week — not a plain interval like "every 2 hours." An entry of 0 */2 * * * means "at minute 0 of every 2nd hour," which is different from "every 2 hours starting from container boot."
Best Practices
- Snapshot the container before you touch the compose file or upgrade the image. Docker upgrades tend to be smooth, but a bad one costs you thirty seconds to roll back instead of an evening rebuilding
- Back up
/opt/speedtest-tracker/dataalong with the container itself — that's where the SQLite database and your entire test history live - Don't set
SPEEDTEST_SCHEDULEtoo aggressively. Running a full speed test every few minutes saturates your connection repeatedly and can annoy anyone else using the network at the time. Every hour or two is plenty for spotting real trends - Set
APP_TIMEZONEandDISPLAY_TIMEZONEbefore you've logged months of data — changing them after the fact doesn't retroactively fix old timestamps
I'd skip exposing this one to the public internet through port forwarding. There's no real benefit to checking your home speed history from outside your house, and it's one more thing sitting on the internet that needs patching.
Frequently Asked Questions
Does Speedtest Tracker slow down my internet while it's running?
Only briefly, and only during an actual test. A full test saturates your connection for anywhere from a few seconds to around a minute depending on your speed tier, then goes back to idle.
Can I run this without Docker, directly on Debian?
The project's own documentation only supports Docker, Docker Compose, Kubernetes, and specific NAS platforms — bare-metal installs aren't officially supported, which is why this guide runs it inside Docker rather than trying to work around that.
How much history does it keep?
All of it, by default, stored in the SQLite database under your mounted /config volume. There's no automatic pruning, so the database will grow slowly over months — it's small text and numbers, not video, so this isn't something you need to worry about for years.
Can I point it at a specific Ookla server instead of letting it pick automatically?
Yes, using the SPEEDTEST_SERVERS environment variable with a server ID. Run docker exec -it speedtest-tracker php artisan app:ookla-list-servers to see server IDs near you before picking one.
Do I need a static IP for the container?
Not strictly, but it makes life easier. If the container's IP changes after a DHCP lease renewal, your bookmarked dashboard URL and your APP_URL environment variable both go stale.
Conclusion
What you've got now is a small, disposable container quietly logging your real internet performance instead of relying on memory and one-off tests. The next time your connection feels off, you won't be guessing — you'll have a graph. Add a second monitor if you're on a dual-WAN setup, or just let this one run in the background and check back in a month; that's usually when the patterns actually start to show up.