Skip to content
New storyWe built Skald to count visitors. It caught an AI-key thief.
SkaldEvery visit, truly told.

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

root@your-server: ~sample
$ 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.

pick one
root@your-server: ~ · edit example.com first
$ 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-data holds everything Skald keeps. Keep the volume.
  • The trusted-proxy line lets Skald see each visitor's address, not your nginx's.

[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.

nginx · inside your site's server block
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;
}
check, then reload
$ 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.

  1. Make the first owner

    It prints a "set your password" link, valid 60 minutes. Change the organisation and the email first.

    edit, then copy
    $ docker exec -it skald skald accounts init --org "Example Co" --owner-email owner@example.com
  2. Fill the last 14 days

    It stores each day your log's rotations still hold, so the charts do not start empty.

    as written
    $ docker exec skald skald backfill --days 14
  3. 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.

    as written
    $ 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

SettingWhat it keeps
SKALD_KEEP_IPS=1Each visit with its IP address, so admins can look a person up. The default for a new install
SKALD_KEEP_IPS=shortEach address with its last part removed: 203.0.113.57 kept as 203.0.113.0
SKALD_KEEP_IPS=0No addresses kept with visits
SKALD_IDENTITY=anonymousNo 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.

TagFollows
:2.30.0exactly that release
:2.30fixes to 2.30 only
:2every 2.x release, never a breaking change
:latestevery release, a new major version too
take a copy, then upgrade
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 upgrade does 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 run gets no certificate: your web server's HTTPS, or Compose's https profile, does that.
  • Without a server log (Shopify, Webflow, Squarespace, or the script tag alone) it sees no AI crawlers and no bots.

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.

Start free trial Live demo