Self-host · Docker · ghcr.io/spcaeo/skald:2
Self-hosted analytics with Docker, in about 10 minutes
Self-hosted analytics with Docker is one container: nginx, the helpers and the schedule inside, your log mounted read-only, your history in one volume.
Self-hosted analytics with Docker means running Skald's image, ghcr.io/spcaeo/skald, on a server you control. The container reads your web server's access log read-only and keeps its history in a Docker volume, so no figure leaves the machine. It is one way to self-host Skald free.
Free to self-host · Elastic License 2.0 · rather not run a server? 30-day free trial, no card
$ docker run -d --name skald \ -p 127.0.0.1:8080:8080 \ -v /var/log/nginx:/logs:ro \ -v skald-data:/var/lib/skald \ ghcr.io/spcaeo/skald:2 $ docker exec skald skald backfill --days 14 stored the days the log still holds $ docker exec skald skald doctor OK Access log /logs/access.log, written today OK Days stored none of the last 14 missing
Sample session. The full command, with your own site's settings, is below.
[01] start it
Run Skald with Docker, Compose or the install script
Pick how your server should run it. Each way runs the same code and keeps the same files. Change example.com, the time zone and the address to your own before you copy.
$ docker run -d --name skald --restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-v /var/log/nginx:/logs:ro \
-v skald-data:/var/lib/skald \
-e SKALD_SITE=example.com \
-e SKALD_HOSTS="example.com www.example.com" \
-e SKALD_TIMEZONE=America/New_York \
-e SKALD_BASE_URL=https://www.example.com \
-e SKALD_TRUSTED_PROXIES=private -e SKALD_REAL_IP_HEADER=X-Real-IP \
ghcr.io/spcaeo/skald:2
127.0.0.1:publishes the port on this machine only. Your web server gives it to the world over HTTPS (next fold).- The log is mounted read-only. It must be readable by group
adm, as it is on stock Debian and Ubuntu. skald-dataholds everything Skald keeps. Keep the volume.- The trusted-proxy line lets Skald see each visitor's address, not your nginx's.
git clone https://github.com/spcaeo/skald && cd skald
cat > .env <<'EOF'
SKALD_SITE=example.com
SKALD_HOSTS=example.com www.example.com
SKALD_TIMEZONE=America/New_York
SKALD_BASE_URL=https://www.example.com
SKALD_LOGS=/var/log/nginx
EOF
docker compose up -d
Skald listens on 127.0.0.1:8080 only. Your web server keeps serving the site and sends Skald's paths to it. Compose already trusts the private ranges and reads X-Real-IP. .env is not committed; the SMTP login goes there too.
git clone https://github.com/spcaeo/skald && cd skald
cat > .env <<'ENV'
SKALD_SITE=example.com
SKALD_HOSTS=example.com www.example.com
SKALD_BASE_URL=https://stats.example.com
SKALD_DOMAIN=stats.example.com
SKALD_TIMEZONE=America/New_York
ENV
docker compose --profile https up -d
For a stats address of its own. Point stats.example.com at the server first and keep ports 80 and 443 free. The bundled Caddy gets and renews the certificate by itself.
$ curl -fsSL https://raw.githubusercontent.com/spcaeo/skald/main/install.sh | sudo bash
For Ubuntu 22.04, 24.04 and 26.04 or Debian 12 and 13, beside the nginx you already run. It makes the skald user (in group adm, to read the log), installs systemd units and cron, and prints the nginx lines to add. It asks for the site, the log, the time zone and the mail address. This runs a script from the internet as root: to read it first, download a release and check it against its SHA256SUMS file, as the install guide shows.
[02] HTTPS in front
Put the dashboard behind your web server's HTTPS
Sign-in needs HTTPS. Your web server already has it, so it sends Skald's paths to the container and serves everything else as before.
Put the block inside your site's HTTPS server { }, before location /. access_log off keeps your own dashboard views out of your visitors. Your website shares this nginx, so always check before you reload.
location ^~ /traffic {
access_log off;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
client_max_body_size 6m;
}
$ sudo nginx -t && sudo systemctl reload nginx
Caddy instead: the matching reverse_proxy block is in the install guide on GitHub.
[03] first run
Sign in, fill 14 days, ask the doctor
Three commands after the container is up. Today fills by itself within two minutes.
-
Make the first owner
It prints a "set your password" link, valid 60 minutes. Change the organisation and the email first.
$ docker exec -it skald skald accounts init --org "Example Co" --owner-email owner@example.com
-
Fill the last 14 days
It stores each day your log's rotations still hold, so the charts do not start empty.
$ docker exec skald skald backfill --days 14
-
Ask the doctor
One line per part, OK, NEEDS ATTENTION or OFF, with the fix. Before the first daily run, "has never run here" is expected.
$ docker exec skald skald doctor
[04] settings
Countries, cities and what Skald keeps
Settings are -e NAME=value lines, or a .env file with Compose. Two groups matter on day one.
nginx's stock log has no country. Give Skald a GeoIP file and each visit gets one. DB-IP's free country and city files work; their credit is shown under the countries. Without a file, countries say Not recorded, never a guess.
-v /usr/share/GeoIP:/geoip:ro -e SKALD_COUNTRY_DB=/geoip/dbip-country-lite.mmdb
-e SKALD_CITY_DB=/geoip/dbip-city-lite.mmdb
| Setting | What it keeps |
|---|---|
SKALD_KEEP_IPS=1 | Each visit with its IP address, so admins can look a person up. The default for a new install |
SKALD_KEEP_IPS=short | Each address with its last part removed: 203.0.113.57 kept as 203.0.113.0 |
SKALD_KEEP_IPS=0 | No addresses kept with visits |
SKALD_IDENTITY=anonymous | No names or emails kept with leads |
Admins can change both later in Settings > Data retention > People. Your privacy notice must say what you keep.
[05] upgrade and back up
Upgrade the Skald image without losing a day
An image is never changed in place: you pull the new tag and start the container again. The volume keeps the data, and the databases are brought up to date when it starts.
| Tag | Follows |
|---|---|
:2.30.0 | exactly that release |
:2.30 | fixes to 2.30 only |
:2 | every 2.x release, never a breaking change |
:latest | every release, a new major version too |
docker exec skald skald backup
docker pull ghcr.io/spcaeo/skald:2
docker rm -f skald
Then start it with the same docker run command as before. Skald also backs itself up every night into the volume; copy those off the server too: docker cp skald:/var/lib/skald/backups ./skald-backups. Never upgrade during the daily run, 07:15 in the site's time zone by default.
What it does not do
skald upgradedoes not run inside a container: with Docker you pull the new tag. Watchtower can do that for you, but it takes no backup and never rolls back.- It runs one copy on one volume. Never start a second container on the same data.
- A plain
docker rungets no certificate: your web server's HTTPS, or Compose'shttpsprofile, does that. - Without a server log (Shopify, Webflow, Squarespace, or the script tag alone) it sees no AI crawlers and no bots.
keep reading
More on self-hosted analytics with Docker
questions
Self-hosted analytics with Docker FAQ
Straight answers, including what Skald does not do.
Which Docker tag should I use for Skald?
Pin ghcr.io/spcaeo/skald:2. It follows every 2.x release, fixes and features, and never a breaking change. :latest follows every release, a new major version too. A tag such as :2.30.0 stays on exactly one release.
Where does the Skald container keep its data?
In one volume mounted at /var/lib/skald: the history, accounts, settings, backups and any shipped logs. Keep that volume. Removing the container keeps the data; only docker volume rm deletes it.
Does Skald change my nginx log or my website?
No. The log is mounted read-only, and Skald only reads it. You add one location block to your web server so Skald's paths reach the container, then check and reload nginx yourself.
Why does sign-in fail on plain http?
The session cookie is Secure, so a browser keeps it only over HTTPS, or at http://localhost. Put your web server's HTTPS in front, or start Compose with the https profile, which adds Caddy and gets a certificate by itself.
How do I upgrade Skald in Docker?
Pull the new image, remove the container and start it again with the same command. The volume keeps every figure, and the databases are brought up to date when the new container starts. Take a copy first with docker exec skald skald backup if you want one. Never upgrade during the daily run.
Can I run Skald without Docker?
Yes. On Ubuntu 22.04, 24.04 and 26.04 or Debian 12 and 13, the install script puts Skald beside your nginx as a system user with systemd and cron. Its skald upgrade command takes a tested backup, checks health and rolls back if a check fails.
$ tail -f access.log | skald
See what AI does with your site. Free for 30 days.
No card. We set up hosted Skald for you within one working day, or you self-host it free. Every line of your log, sorted and added up.