Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Deploying 4got

Quick Start

git clone https://github.com/your-user/4got.git && cd 4got
cp data/config.default.kdl data/config.kdl   # edit owner-secret!
docker compose up -d

Open http://localhost:8888 — you have a working search engine. See First-run configuration to set your admin secret and API keys.

Docker Compose

The recommended way to run 4got. The docker-compose.yml builds both the Go server and the optional Python sidecar.

# Start the server only (no browser-backed engines)
docker compose up -d

# Start with the Python sidecar for Google/Qwant/Yandex JS scraping
docker compose --profile sidecar up -d

# View logs
docker compose logs -f server

# Stop everything
docker compose down

Configuration lives in data/config.kdl (mounted as a volume). The SQLite database (data/4got.db) is created automatically on first run.

To customise ports, API keys, or peer settings, copy the override example and edit it:

cp docker-compose.override.yml.example docker-compose.override.yml
# edit docker-compose.override.yml, then:
docker compose up -d

First-run configuration

  1. Copy data/config.default.kdl to data/config.kdl if you haven’t already
  2. Set owner-secret to a strong random string — this is your admin password:
    python3 -c "import secrets; print(secrets.token_urlsafe(32))"
    
  3. Add API keys in the api-keys block (all optional):
    • wolframalpha — instant answers
    • deepl — translation widget
    • spotify-client-id / spotify-client-secret — music results
  4. Configure engine groups in data/groups.kdl (the defaults are sensible)
  5. Start the server and visit /admin/login to authenticate as owner
  6. Optionally set peer-secret and configure data/peers.kdl for federated search across multiple instances

Prerequisites

  • Go 1.23+ (the go.mod targets 1.25 but 1.23+ should work)
  • SQLite3 development headers (e.g. libsqlite3-dev on Debian, sqlite-dev on Alpine)
  • GCC or a C compiler (required by go-sqlite3’s CGO binding)
  • cjxl (optional) for JPEG XL captcha and image proxy reencoding — part of libjxl
  • Playwright (optional) for the Python sidecar that handles JS-rendered engine scraping

Building from source

cd go-server
go build -o 4got .

CGO must be enabled (it is by default). If cross-compiling, set CGO_ENABLED=1 explicitly:

CGO_ENABLED=1 go build -o 4got .

The binary is fully self-contained. Copy it alongside the data/, static/, and templates/ directories.

Directory layout

4got/
  go-server/
    4got          # the binary
  data/
    config.kdl    # main configuration
    *.kdl         # subsystem configs (peers, groups, captcha, etc.)
    4got.db       # SQLite database (created automatically)
  static/         # CSS, JS, fonts
  templates/      # HTML templates

The binary looks for data/config.kdl (relative to CWD) or ../data/config.kdl (when running from go-server/). Override with FGOT_CONFIG=/path/to/config.kdl.

Running directly

cd /path/to/4got
./go-server/4got

By default it listens on 0.0.0.0:8888. Override via config.kdl or environment variables:

FGOT_PORT=9999 FGOT_HOST=127.0.0.1 ./go-server/4got

Running with systemd

A production-ready unit file ships in the repo at 4got.service. Install it:

sudo cp 4got.service /etc/systemd/system/4got.service
sudo systemctl daemon-reload
sudo systemctl enable --now 4got
sudo journalctl -u 4got -f

The unit file targets network-online.target, runs as user/group 4got, applies security hardening (ProtectSystem=strict, ProtectHome=yes, NoNewPrivileges, PrivateTmp), and sets LimitNOFILE=65536 for high-concurrency deployments. Edit ReadWritePaths if your data directory is not /opt/4got/data.

Reverse proxy

nginx

server {
    listen 443 ssl http2;
    server_name search.example.com;

    ssl_certificate     /etc/letsencrypt/live/search.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/search.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE support (AI research agent)
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 120s;
    }

    # Optional: serve static files directly
    location /static/ {
        alias /opt/4got/static/;
        expires 7d;
    }
}

Caddy

search.example.com {
    reverse_proxy 127.0.0.1:8888
}

Caddy handles TLS automatically via Let’s Encrypt.

Docker / Podman

A multi-stage Dockerfile is provided in the project root, based on Debian bookworm-slim (glibc required for CGO/SQLite):

docker build -t 4got .
docker run -d -p 8888:8888 -v 4got-data:/data 4got

Podman is a drop-in replacement – the same commands work with podman instead of docker.

Mount a volume at /data to persist the SQLite database, configuration, and cache across restarts. The docker-compose.yml handles this automatically:

docker compose up -d

Codebase size comparison

Lines of code as of May 2026. Measured with wc -l against source files, excluding vendored dependencies.

ComponentLines
4got Go source (minus tests)20,866
4got test code5,324
4got Go total26,190
4got KDL configs (engine defs, etc.)10,682
4got Python sidecar819
4got templates (HTML)3,326
4got static (JS + CSS)3,299
4get PHP (reference implementation)35,480
SearXNG Python (minus tests)47,425
SearXNG test code3,764
SearXNG Jinja templates2,263

4got’s Go core is ~40% smaller than 4get’s PHP while covering more categories (15 vs 4get’s handful) and adding federation, AI research, oracles, and multi-backend translation. The engine count difference is largely engines.kdl (274 engines declared in config, not code).

PaaS providers

SQLite needs persistent storage on every PaaS. Ephemeral filesystems lose your cache, stats, and search history on every redeploy. Always mount a volume at /data.

Fly.io

A fly.toml is included in the repo root. Create a volume before deploying:

fly launch          # creates the app, detects the Dockerfile
fly volumes create 4got_data --region iad --size 1
fly deploy

The fly.toml mounts the volume at /data and exposes port 8888 internally. Fly handles TLS and routing automatically.

Railway

Railway auto-detects the Dockerfile. Set the PORT environment variable to 8888 in your service settings. Attach a persistent volume mounted at /data to keep SQLite state across deploys.

Render

Create a new Web Service with Docker deployment. Point it at the repo, then add a persistent disk mounted at /data (minimum 1 GB). Set the HTTP port to 8888 in the service configuration.

Coolify

Self-hosted PaaS. Point Coolify at the git repo, it will build from the Dockerfile. Add a persistent volume mapped to /data. No special configuration needed beyond that.

LXC / bare metal

Install Go 1.25+ and a C compiler (GCC), then build from source:

cd go-server
CGO_ENABLED=1 go build -o 4got .

Copy the binary alongside the required directories to the target machine:

rsync -a go-server/4got go-server/templates/ static/ data/ docs/ target:/opt/4got/

The binary runs from the project root and expects data/, static/, and go-server/templates/ as siblings (it searches multiple template paths automatically). See Running with systemd above for a production service file.

Install tesseract-ocr on the host if you want file analysis OCR support.

DNS privacy

To prevent search query leakage to your ISP via DNS resolution, run dnscrypt-proxy in front of your system resolver (Technitium, Unbound, or systemd-resolved). This encrypts outgoing DNS queries to upstream resolvers.

Recommended setup:

  1. Install dnscrypt-proxy and point it at trusted DoH/DoT servers (e.g. Quad9 9.9.9.9, Cloudflare 1.1.1.1).
  2. Configure your local resolver (Technitium/Unbound) to forward to 127.0.0.1:5353 (dnscrypt-proxy’s default listen port).
  3. Ensure 4got’s host uses this resolver chain so engine queries don’t leak plaintext DNS to your ISP.

Or just point Claude Code at this repo and ask it to help you deploy

claude "Deploy 4got to my server at /opt/4got. Set up systemd, nginx reverse proxy on search.example.com, and configure a Wolfram Alpha API key."

Runtime dependencies for banner uploads

Banner transcoding shells out to cjxl (stills → progressive JXL, metadata stripped) and ffmpeg with libaom (animated GIF → animated AVIF). On Debian: apt install libjxl-tools ffmpeg. Without them, banner uploads fail with “Transcoding failed” — searches are unaffected.