SerpTrail is a self-hosted SEO and GEO rank tracker built with Ruby on Rails and SQLite.
467
SerpTrail is a self-hosted SEO rank tracker built with Ruby on Rails and SQLite. It helps you monitor how your websites rank for important keywords across locations, keep historical position data, and inspect SERP snapshots over time.
It uses SerpApi for Google search results and includes an optional AI chat assistant powered by OpenAI via RubyLLM. The assistant can answer questions about your tracked keywords, historical rankings, competitors, and live search data when API keys are configured.
docker pull serpapi/serptrail:latest
The container listens on port 80.
SerpTrail’s production configuration assumes it is running behind an HTTPS-terminating reverse proxy. For real deployments, put it behind Caddy, Traefik, Nginx, Kamal Proxy, Cloudflare Tunnel, or another TLS proxy.
SerpTrail stores API keys encrypted in the database, so Active Record encryption keys must stay stable for the lifetime of your data.
| Variable | Required | Description |
|---|---|---|
SECRET_KEY_BASE | Yes | Rails secret used for signing/encryption. Generate once and keep stable. |
HTTP_AUTH_USERNAME | Yes | Username for built-in HTTP Basic Auth. |
HTTP_AUTH_PASSWORD | Yes | Password for built-in HTTP Basic Auth. |
AR_ENCRYPTION_PRIMARY_KEY | Yes | Rails Active Record encryption primary key. |
AR_ENCRYPTION_DETERMINISTIC_KEY | Yes | Rails Active Record encryption deterministic key. |
AR_ENCRYPTION_KEY_DERIVATION_SALT | Yes | Rails Active Record encryption salt. |
SOLID_QUEUE_IN_PUMA | Recommended | Set to true for a simple one-container deployment with background jobs in the web process. |
SERPAPI_API_KEY | Optional | Initial SerpApi key seed value. You can also set it later in /settings. |
OPENAI_API_KEY | Optional | Initial OpenAI key seed value / fallback for chat. You can also set it later in /settings. |
RAILS_LOG_LEVEL | Optional | Defaults to info. Use debug, warn, or error as needed. |
Generate SECRET_KEY_BASE with Rails:
docker run --rm serpapi/serptrail:latest bin/rails secret
Generate Active Record encryption keys with Rails:
docker run --rm serpapi/serptrail:latest bin/rails db:encryption:init
The encryption command prints values for:
AR_ENCRYPTION_PRIMARY_KEY
AR_ENCRYPTION_DETERMINISTIC_KEY
AR_ENCRYPTION_KEY_DERIVATION_SALT
docker runCreate a persistent volume for SQLite databases and Active Storage files:
docker volume create serptrail_storage
Run the app:
docker run -d \
--name serptrail \
-p 80:80 \
-v serptrail_storage:/rails/storage \
-e SECRET_KEY_BASE="change-me-generate-a-long-random-value" \
-e HTTP_AUTH_USERNAME="admin" \
-e HTTP_AUTH_PASSWORD="change-me" \
-e AR_ENCRYPTION_PRIMARY_KEY="change-me-generate-once" \
-e AR_ENCRYPTION_DETERMINISTIC_KEY="change-me-generate-once" \
-e AR_ENCRYPTION_KEY_DERIVATION_SALT="change-me-generate-once" \
-e SOLID_QUEUE_IN_PUMA="true" \
serpapi/serptrail:latest
The container entrypoint runs database preparation automatically before the Rails server starts.
Open your deployed URL and sign in with the HTTP Basic Auth credentials you configured.
After the container starts:
/settings.Create docker-compose.yml:
services:
web:
image: serpapi/serptrail:latest
ports:
- "80:80"
environment:
SECRET_KEY_BASE: "change-me-generate-a-long-random-value"
HTTP_AUTH_USERNAME: "admin"
HTTP_AUTH_PASSWORD: "change-me"
AR_ENCRYPTION_PRIMARY_KEY: "change-me-generate-once"
AR_ENCRYPTION_DETERMINISTIC_KEY: "change-me-generate-once"
AR_ENCRYPTION_KEY_DERIVATION_SALT: "change-me-generate-once"
SOLID_QUEUE_IN_PUMA: "true"
# Optional initial values; these can also be configured in /settings.
# SERPAPI_API_KEY: "your-serpapi-key"
# OPENAI_API_KEY: "your-openai-key"
volumes:
- serptrail_storage:/rails/storage
restart: unless-stopped
volumes:
serptrail_storage:
Start it:
docker compose up -d
Tail logs:
docker compose logs -f
Stop it:
docker compose down
Data remains in the serptrail_storage volume. To delete all app data too, run:
docker compose down -v
For a small single-container deployment, SOLID_QUEUE_IN_PUMA=true is the easiest option.
For larger deployments, run jobs in a separate container instead:
services:
web:
image: serpapi/serptrail:latest
ports:
- "80:80"
environment:
SOLID_QUEUE_IN_PUMA: "false"
# include the same required env vars as above
volumes:
- serptrail_storage:/rails/storage
restart: unless-stopped
jobs:
image: serpapi/serptrail:latest
command: ./bin/jobs
environment:
# include the same required env vars as web
volumes:
- serptrail_storage:/rails/storage
depends_on:
- web
restart: unless-stopped
volumes:
serptrail_storage:
Both containers must share the same /rails/storage volume because SerpTrail uses SQLite.
SerpTrail stores its SQLite databases and local files under:
/rails/storage
Back up the Docker volume mounted at that path. Do not rotate the Active Record encryption keys unless you are prepared to lose access to encrypted values already stored in the database.
SerpTrail exposes a basic Rails health endpoint:
/up
Built by SerpApi.
Content type
Image
Digest
sha256:121f14035…
Size
204.5 MB
Last updated
2 months ago
docker pull serpapi/serptrail