Self-hostable uptime monitor and status page on Django: HTTP checks, Lighthouse audits, SEO crawls, and email and Discord alerts.
djangodockerhandcodedpythonself-hostedsqlitestatus-pageuptime-monitoringvite
1> **Archived.** This project is no longer in use and no longer maintained. Last updated April 2026.
2
3# Status
4
5A self-hostable uptime monitor and status page. HTTP checks every 3 minutes,
6daily Lighthouse audits, weekly in-process SEO crawls, and alerts via email
7and Discord webhook on state transitions.
8
9
10## Motivation
11
12I was bored and felt like writing my own uptime service over the weekend.
13
14
15## Features
16
17- HTTP uptime checks with rolling uptime percentages and recent-uptime bars
18- Lighthouse audits (performance, accessibility, best practices, SEO) with
19 weighted breakdown and top savings opportunities
20- In-process SEO crawler (requests + BeautifulSoup) — title, description,
21 canonical, OG tags, and H1 per page
22- Security header analysis (HSTS, CSP, X-Frame-Options, Referrer-Policy, etc.)
23- Alert state machine with debounce on flaps — two consecutive non-200s to
24 go down, immediate 200 to come back up
25- Email and Discord webhook alerts on state transitions only
26- PDF report export per property via a headless Chromium subprocess
27- Customizable UI with a warm-earth palette and Monaspace Argon
28
29
30## Requirements
31
32You need docker + docker-compose installed for a quick production start or you
33can figure out how we install and run things via the `Dockerfile` and set it up
34yourself.
35
36If you want to install things without docker then you'll need the following
37dependencies:
38
39- python
40- uv
41- bun
42- node (required only for the `lighthouse` npm CLI — Bun doesn't run Lighthouse
43 correctly; see bun issue #4958)
44- chromium (used for PDF report generation via a subprocess wrapper)
45
46You can also check the `Dockerfile` for an exact list of dependencies and adjust
47package names for your desired platform.
48
49This is a standard Django project. If you know how to run Django, or want to
50look up any Django tutorial on how to run Django, you shouldn't have a problem
51getting this project running on almost anything.
52
53
54## Running locally
55
56If you have all of the above dependencies installed you can use my Makefile to
57run and install python and node dependencies locally. Running `make` will check
58that you have the proper dependencies installed and if not it will try and
59install them for you. It will then create you a fresh database and run
60everything.
61
62If you want to also run the scheduler you'll have to do so separately. Run
63`make` in one window/tab and run `make scheduler` in another.
64
65
66## Checking outdated dependencies
67
68This can be done in both bun and uv with the following two commands:
69
70 uv lock --upgrade --dry-run
71 bun outdated
72
73You can then upgrade all dependencies at once with:
74
75 make update
76
77I recommend testing everything after this to make sure it's all working.
78
79
80## Optimizing images with webp
81
82My development system runs Ubuntu so I installed the official webp utils from
83Google with `apt install webp`.
84
85 cwebp -q 90 -m 6 -o output.webp input.png
86
87
88## Using docker-compose
89
90The easiest way to run this project is to run it using
91`docker-compose up --build -d` if you have `docker-compose` and `docker`
92installed. This will start the server and have you running at port 8000. The
93first time you do this make sure you run migrations with
94`docker-compose run web python manage.py migrate`. Make sure you setup the
95`.env` file before running, you can copy the sample from
96`samplefiles/env.sample` into the root of the project as `.env` and change the
97variables.
98
99
100## Default user
101
102The default user is `admin` with the password `admin`. Add your own properties
103from the dashboard after signing in.
104
105
106## Alerts
107
108Each property can be assigned a Discord webhook URL per user (see the account
109settings). Email alerts use the project's configured outbound mailer (the
110direct-to-MX backend by default, see `status/mailer.py`). Alerts fire on state
111transitions only — not on every failing check.
112
113
114## Backups
115
116All data is stored in `/srv/data/status/` and your repo is stored in
117`/srv/git/status.git/`. You can backup both of these folders and you'll have
118a 100% backup of everything except changes you may have made to the `Caddyfile`
119and the `.env` file which should be easy enough to recreate but you can back
120those up too!
121
122
123## Server guide
124
125This quickstart requires that you have an Alpine Linux server running with a
126domain name pointed to it. I'm currently using Linode as my host since they
127support Alpine Linux nicely. If you don't want to use Linode or Alpine Linux
128you can use these instructions and just change the apk commands at the start to
129whatever Linux distro you're using.
130
131**IMPORTANT NOTE**: Change `status.bythewood.me` to your domain name where
132relevant in these instructions.
133
134**TIP**: During the ufw portion to enable the firewall I recommend only allowing
135your IP address or your ISP's IP address range which you can find on whois
136lookups at the top. For example, replace `192.230.176.0/20` with your IP or your
137ISP's IP range.
138
139 ufw allow from 192.230.176.0/20 proto tcp to any port 22
140
141I allow my local ISP's range because I have a DHCP lease from them and I get
142tired of logging into my server from my hosting provider's UI to update it. It's
143good enough security and much better than nothing!
144
145Server:
146
147 apk update && apk upgrade && apk add docker docker-compose caddy git iptables ip6tables ufw
148 ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw --force enable
149 echo -e "#!/bin/sh\napk upgrade --update | sed \"s/^/[\`date\`] /\" >> /var/log/apk-autoupgrade.log" > /etc/periodic/daily/apk-autoupgrade && chmod 700 /etc/periodic/daily/apk-autoupgrade
150 rc-update add docker boot && service docker start
151 mkdir -p /srv/git/status.git && cd /srv/git/status.git && git init --bare
152
153Local:
154
155 git clone [email protected]:overshard/status-django.git && cd status-django
156 git remote remove origin && git remote add origin [email protected]:/srv/git/status.git
157 git push --set-upstream origin master
158
159Server:
160
161 mkdir -p /srv/docker && cd /srv/docker && git clone /srv/git/status.git status && cd /srv/docker/status
162 cp samplefiles/Caddyfile.sample /etc/caddy/Caddyfile && sed -i 's/status.example.com/status.bythewood.me/g' /etc/caddy/Caddyfile
163 cp samplefiles/env.sample .env && sed -i 's/status.example.com/status.bythewood.me/g' .env
164 cp samplefiles/post-receive.sample /srv/git/status.git/hooks/post-receive
165 mkdir -p /srv/data/status/db && chown -R 1000:1000 /srv/data/status
166 docker-compose up --build --detach && docker-compose run web python3 manage.py migrate --noinput && docker-compose run web sqlite3 db.sqlite3 "PRAGMA journal_mode=WAL;" ".exit"
167 rc-update add caddy boot && service caddy start
168
169
170## Scaling
171
172I choose to use an sqlite3 database since that handles all my usecases just
173fine. My first recommendation for scaling this project would be to use a
174PostgreSQL database. If you want to get fancy then a time-series database like
175Timescale would make a lot of sense. The foundation of this project is pure
176Django so it shouldn't be hard to swap in a different database.
177
178
179## Support
180
181I won't be providing any user support for this project. I'm more than happy to
182accept good pull requests and fix bugs but I don't have the time to help people
183run or use this project. I appologize in advance for this. Maintaining
184mutliple OSS projects has taught me that I need to step back from trying to
185provide support to avoid burnout.