---
title: "Deploy GoatCounter"
description: "Web analytics that counts visits without cookies or personal data"
category: "Analytics"
url: https://railway.com/deploy/goatcounter-analytics
---

# Deploy GoatCounter

Web analytics that counts visits without cookies or personal data

**[Deploy GoatCounter on Railway](https://railway.com/template/goatcounter-analytics)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/goatcounter-analytics/manifest.json

- **Creator:** A3A
- **Category:** Analytics
- **Total deploys:** 1

## Template content

### Postgres https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/postgresql.svg

- **Image:** ghcr.io/railwayapp-templates/postgres-ssl:18

### goatcounter https://cdn.jsdelivr.net/gh/arp242/goatcounter@v2.7.0/public/logo.svg

- **Source:** https://github.com/gridalpha/goatcounter-railway
- **Health check:** /status

### gateway https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/caddy.svg

- **Source:** https://github.com/gridalpha/goatcounter-railway
- **Health check:** /healthz
- **Public domain:** Yes

## Documentation

# 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](https://res.cloudinary.com/rroe4rtk/image/upload/f_auto,q_auto/v1789459032/goatcounter-architecture.webp)

## 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](https://res.cloudinary.com/rroe4rtk/image/upload/f_auto,q_auto/v1789459034/goatcounter-dashboard.webp)
![Referrer, browser, operating system and location breakdowns](https://res.cloudinary.com/rroe4rtk/image/upload/f_auto,q_auto/v1789459036/goatcounter-breakdowns.webp)
![GoatCounter API token settings with a saved reporting token](https://res.cloudinary.com/rroe4rtk/image/upload/f_auto,q_auto/v1789459037/goatcounter-api-token.webp)

## 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](https://github.com/gridalpha/goatcounter-railway) on top of the official [`arp242/goatcounter`](https://hub.docker.com/r/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.
- **gateway** — `caddy:2-alpine` from the same repository, and the only service with a public domain.

### Environment Variables Reference

| Variable | Service | Purpose |
|---|---|---|
| `GC_ADMIN_EMAIL` | goatcounter | Login for the administrator created on first boot |
| `GC_ADMIN_PASSWORD` | goatcounter | That account's password, minimum 8 characters |
| `GC_VHOST` | goatcounter | Domain visitors use; a site is selected by request host |
| `GOATCOUNTER_DB` | goatcounter | Database connection, as `postgresql+` |
| `GOATCOUNTER_SMTP` | goatcounter | SMTP 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.

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 0.5 vCPU | 1–2 vCPU |
| RAM | 512 MB | 1–2 GB |
| Storage | 1 GB volume | 5 GB volume plus database growth |
| Runtime | Go 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 &gt; 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.


## Similar templates

- [Typesense vs Meilisearch](https://railway.com/deploy/typesense-vs-meilisearch) — self-hosted Typesense vs Meilisearch
- [Betterlytics](https://railway.com/deploy/betterlytics) — Betterlytics is a cookieless analytics platform GDPR-compliant.
- [Finance Tracker](https://railway.com/deploy/finance-tracker-1) — Private multi-user household finance ledger with budgets and CSV import.

Open this page in a browser: https://railway.com/deploy/goatcounter-analytics
