1. Run it (2 minutes)
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/traffic-intel \
-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. nginx in front gives it to the world. Keep the same line in every laterdocker run./var/log/nginxis read-only: Skald reads the log, never changes it.skald-dataholds everything Skald keeps. It must be mounted at/var/lib/traffic-intel, where the image keeps its data. Keep the volume.:2follows every 2.x release and never a breaking one.- Not a Next.js site? Add
-e SKALD_ASSET_PREFIXES="/wp-content/ /wp-includes/"(WordPress), or the folder your pages load their scripts and styles from. A visit counts as people only when its browser fetched one. - The last line is for nginx on the same machine. It lets Skald see each visitor's address, not nginx's.
- Countries: nginx's stock log has no country. To get one, give Skald a GeoIP country file. DB-IP's free country file works. Keep it on the host, for example in
/usr/share/GeoIP, and add-v /usr/share/GeoIP:/geoip:ro -e SKALD_COUNTRY_DB=/geoip/dbip-country-lite.mmdb. Each visit then gets its address's country, and so does a lead your backend sends with an address. Refresh the file on the host once a month (your updater or a cron job that downloads DB-IP's new file). Skald reads the new file by itself, no restart. DB-IP's credit is shown under the countries. Without a file, countries say Not recorded. A country your log already carries (Cloudflare's, or nginx's owncc=) always wins. - A new install keeps each visit with its IP address, and each lead's name and email, so admins can look a person up. To keep neither, add
-e SKALD_KEEP_IPS=0 -e SKALD_IDENTITY=anonymous. To keep each address shortened, with its last part removed (203.0.113.57 is kept as 203.0.113.0), add-e SKALD_KEEP_IPS=shortinstead. Admins can change both later in Settings > Data retention > People. See Privacy.
2. Put it on the web (2 minutes)
In your site's HTTPS server { } block, before location /:
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;
}
Then check and reload. Careful: your website shares this nginx, so never reload without the check.
sudo nginx -t && sudo systemctl reload nginx
3. Sign in (1 minute)
docker exec -it skald traffic-intel accounts init --org "Example Co" --owner-email owner@example.com
It prints a "set your password" link, valid 60 minutes. Open it, set a password, and you are in at https://www.example.com/traffic/. See Sign-in and team to invite others.
4. Fill the last 14 days (2 minutes)
docker exec skald traffic-intel backfill --days 14
It stores each day the log's rotations still hold. Today fills by itself within two minutes.
5. Ask the doctor (1 minute)
docker exec skald traffic-intel doctor
One line per part, each OK, NEEDS ATTENTION or OFF, and the fix in plain words:
| Line | Checks |
|---|---|
| Daily run | It ran in the last day, and did not fail |
| Days stored | None of the last 14 days is missing or withheld; if some are, the backfill command |
| Access log | The log is there, readable, and written today |
| Page assets | A browser fetched your page assets: without them nobody counts as a person |
| Web app | The dashboard has its menu |
| Hook receiver | The part that takes hooks and the script tag answers |
| Hook ... | Each hook: its secret, and its last delivery |
| One lead, two tools | The same lead arrived through two tools (two hooks, or a hook and the lead database), so it counts twice |
| Script tag | Its origins, and the last event from a page |
| Lead database | The website's lead database can be read, when one is set |
| The mail settings are complete | |
| Version | A newer release, from the last daily check (it never goes online itself) |
| Disk | Free space where Skald keeps its data |
OFF is a part you have not turned on. It exits 1 when anything needs attention, so a script can watch it. --json prints the same list as JSON. --send-test also sends one test mail to the recipients.
The daily run happens at 07:15 in the site's time zone. Before the first one, "Daily run: has never run here" is expected.
6. Add the script tag (2 minutes)
The tag gives form steps, clicks and booking widgets. Start the container again with your site's origin:
docker rm -f skald
docker run ... -e SKALD_BEACON_ORIGINS=https://www.example.com ... # the same command, plus this line
Then copy the tag from Settings > Integrations into every page's head. See Tracking script.
Only the first site needs SKALD_BEACON_ORIGINS. A site you add later from the dashboard takes events from its own Host names (Settings > This site > Sites). Its page assets are its own too: a site that is not Next.js sets them in Settings > This site > Sites > Page assets (SKALD_ASSET_PREFIXES is for the first site only). See Tracking script, "More than one site".
7. Add hooks
Settings > Integrations > Make secret for each tool, or on the server:
docker exec skald traffic-intel hooks secret example.com lead
Then: Send leads from your backend, Demo bookings, and Webhooks for the other tools. Not sure which? Pick your road.
Upgrade
docker pull ghcr.io/spcaeo/skald:2
docker rm -f skald
docker run ... # the same command as before
The volume keeps all data. Not during the daily run.
When something is wrong
| You see | Why | Fix |
|---|---|---|
| A plain page with no menu | An old image | Pull the new image; the doctor's Web app line says so |
| "nothing parsed" | The log is not mounted, or not readable | docker exec skald ls -l /logs; the container's user is in group adm (gid 4), which reads /var/log/nginx on Ubuntu and Debian |
| No person counted | No page asset seen | Set SKALD_ASSET_PREFIXES (the first site) or Settings > Sites > Page assets (a site added later); the doctor's Page assets line says which |
| A hook answers 404 | The hook has no secret | Make its secret |
| A hook answers 502 | An image older than 2.2.2 | Pull the new image |
| "Too many tries" on sign-in for everyone | The proxy in front is not trusted | Set SKALD_TRUSTED_PROXIES and SKALD_REAL_IP_HEADER |
docker logs skald shows one line per job.