Deploy GoatCounter

Web analytics that counts visits without cookies or personal data

Deploy GoatCounter

/var/lib/postgresql/data

Just deployed

/home/goatcounter/goatcounter-data

Just deployed

Deploy and Host GoatCounter on Railway

GoatCounter is an open-source web analytics platform that counts what happens on your site without tracking the people who visit it. It sets no cookies and stores no personal data, identifying unique visits through a rotating, non-identifiable hash, so it generally needs no consent banner. The snippet is about 3.5 KB, and there is a JavaScript-free pixel and a logfile importer too.

Deploy GoatCounter on Railway and you get the production shape, not the smallest thing that boots. Three services come up together: goatcounter runs the Go binary with a persistent volume, Postgres stores every pageview, and gateway is the public front door that normalises visitor IP addresses. Traffic arrives at the gateway and is forwarded privately to GoatCounter, which reads and writes Postgres over the private network — only the gateway is reachable from the internet. Your first site and administrator account are created during the first boot, so there is never a moment when a stranger can claim the instance.

Diagram of the GoatCounter, gateway and Postgres services on Railway

Getting Started with GoatCounter on Railway

Set GC_ADMIN_EMAIL and GC_ADMIN_PASSWORD before deploying — they become your login, and the password needs at least eight characters. Everything else has a working default. The generated URL then opens a sign-in page rather than a setup wizard, because the account already exists.

Sign in and the dashboard will be empty. Open Help → Site code for the tracking snippet, which already points at your own deployment, and paste it before the closing `` tag on your site. Reload one of your pages, wait about ten seconds for GoatCounter to flush its buffer, then refresh the dashboard: the pageview appears under Pages, with referrer, browser, system and country broken out below. To check the deployment without touching your site, open /status, which returns the running GoatCounter and PostgreSQL versions as JSON.

GoatCounter dashboard listing tracked pages and visit totals Referrer, browser, operating system and location breakdowns GoatCounter API token settings with a saved reporting token

About Hosting GoatCounter

Most analytics tools answer questions you never asked and hide the two or three you did. GoatCounter shows pages, visitors, referrers, browsers, systems, screen sizes and countries, and stops there.

  • Cookie-free unique-visit detection using a daily-rotating hash
  • Tracking by script tag, pixel, backend API call or server logfile import
  • Referrer, campaign, browser, system, screen-size and country breakdowns
  • Path filtering, custom date ranges, CSV/JSON exports and a REST API
  • Multiple sites from one installation, each on its own domain

The architecture splits three concerns. goatcounter is a statically compiled Go binary that runs its scheduled tasks in-process, so there is no separate worker; its volume holds exports and an optional MaxMind database. Postgres is the system of record — SQLite also works, but PostgreSQL is what upstream recommends once pageviews spread across many distinct URLs. gateway is a small Caddy service that strips client-supplied CF-Connecting-IP, Fly-Client-IP, X-Azure-SocketIP and True-Client-IP headers and substitutes the real connection address, because GoatCounter trusts those headers ahead of everything else with no way to reorder them.

Why Deploy GoatCounter on Railway

Railway removes the parts of self-hosting analytics that are not analytics.

  • PostgreSQL provisioned, connected and backed up alongside the app
  • A persistent volume already mounted for exports and GeoIP data
  • HTTPS and a public domain issued automatically
  • Your administrator account created on first boot, never left open to claim

Common Use Cases

  • Replacing Google Analytics on a blog or docs site, where a consent banner would otherwise be the only GDPR work required
  • Giving a SaaS product usage numbers without shipping a third-party tracker to customers
  • Measuring which launch posts drove signups, through the referrer and campaign views
  • Backfilling history by importing existing nginx or Caddy access logs

Dependencies for GoatCounter

  • goatcounter — built from gridalpha/goatcounter-railway on top of the official arp242/goatcounter image, currently 2.7.0. It adds a startup script that runs migrations, creates the first site and administrator, then drops privileges.
  • Postgres — Railway's managed PostgreSQL 18 (ghcr.io/railwayapp-templates/postgres-ssl:18), holding sites, users, pageviews and aggregate tables.
  • gatewaycaddy:2-alpine from the same repository, and the only service with a public domain.

Environment Variables Reference

VariableServicePurpose
GC_ADMIN_EMAILgoatcounterLogin for the administrator created on first boot
GC_ADMIN_PASSWORDgoatcounterThat account's password, minimum 8 characters
GC_VHOSTgoatcounterDomain visitors use; a site is selected by request host
GOATCOUNTER_DBgoatcounterDatabase connection, as postgresql+
GOATCOUNTER_SMTPgoatcounterSMTP relay URL; defaults to printing mail to the log

Every flag of goatcounter serve can be set this way, as GOATCOUNTER_ — including GOATCOUNTER_GEODB for city-level location and GOATCOUNTER_RATELIMIT. Leave GOATCOUNTER_LISTEN and GOATCOUNTER_TLS alone; the startup script sets both for Railway's edge.

Deployment Dependencies

  • Source repository:
  • Upstream project and docs: ,
  • Official image:

Hardware Requirements for Self-Hosting GoatCounter

GoatCounter is a single Go binary and unusually light; PostgreSQL is what grows.

ResourceMinimumRecommended
CPU0.5 vCPU1–2 vCPU
RAM512 MB1–2 GB
Storage1 GB volume5 GB volume plus database growth
RuntimeGo 1.25 (compiled in)Same, with PostgreSQL 18

Throughput depends less on how many pageviews you record than on how many distinct URLs they spread across, which is what makes PostgreSQL worth having.

Self-Hosting GoatCounter

The release binaries are statically linked and need only somewhere to keep a database. To run one against SQLite:

curl -sL https://github.com/arp242/goatcounter/releases/download/v2.7.0/goatcounter-v2.7.0-linux-amd64.gz | gunzip > goatcounter
chmod +x goatcounter
./goatcounter db create site -vhost stats.example.com -user.email you@example.com
./goatcounter serve -listen :8080

With Docker and PostgreSQL, which is what this template runs:

docker run -d --name goatcounter -p 8080:8080 \
  -v goatcounter-data:/home/goatcounter/goatcounter-data \
  -e GOATCOUNTER_DB='postgresql+postgresql://user:pass@postgres:5432/goatcounter?sslmode=require' \
  arp242/goatcounter serve -automigrate

Create the first site with docker exec -it goatcounter goatcounter db create site -vhost stats.example.com -user.email you@example.com before exposing the container: until one exists, GoatCounter serves an open account-creation form at /. Handling that during startup is the main thing the source repository adds.

Is GoatCounter Free to Self-Host?

GoatCounter is free and open source, with no licence fee, seat limit or pageview cap. The project also runs a hosted service, free for non-commercial use with paid business plans. On Railway you pay only for the compute, memory and storage the three services consume — the app idles well under 100 MB.

FAQ

What is GoatCounter?

An open-source, privacy-friendly web analytics tool. It records pageviews, referrers, browsers, systems, screen sizes and countries without cookies or any persistent identifier, which is why it generally needs no GDPR consent notice.

What does this Railway template deploy?

Three services: GoatCounter on a persistent volume, a managed PostgreSQL database, and a Caddy gateway holding the public domain. Your administrator account is created during the first boot from the email and password you supply.

Why does the template include PostgreSQL instead of SQLite?

Both work, and SQLite is fine for a small site. PostgreSQL is what upstream recommends once pageviews spread across many distinct URLs, and on Railway it puts your data in a managed, backed-up database rather than on one volume.

Why is there a separate gateway service in front of GoatCounter?

GoatCounter reads several vendor-specific client-IP headers ahead of everything else, with no setting to change that order. Those headers are not used on Railway, so a visitor could send one and pick the IP recorded against their visit. The gateway removes them and substitutes the real connection address, keeping location statistics honest and the rate limits effective.

Can I use a custom domain with self-hosted GoatCounter?

Yes. Add it to the gateway service, then update the site's domain under Settings → Site settings so the two match, since GoatCounter picks a site from the request host. To send password-reset mail, set GOATCOUNTER_SMTP to an SMTP URL.


Template Content

More templates in this category

View Template
Typesense vs Meilisearch
self-hosted Typesense vs Meilisearch

onepush
0
View Template
Betterlytics
Betterlytics is a cookieless analytics platform GDPR-compliant.

OpenSource Templates
27
View Template
Finance Tracker
Private multi-user household finance ledger with budgets and CSV import.

wotonews
0