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
- Copy
data/config.default.kdltodata/config.kdlif you haven’t already - Set
owner-secretto a strong random string — this is your admin password:python3 -c "import secrets; print(secrets.token_urlsafe(32))" - Add API keys in the
api-keysblock (all optional):wolframalpha— instant answersdeepl— translation widgetspotify-client-id/spotify-client-secret— music results
- Configure engine groups in
data/groups.kdl(the defaults are sensible) - Start the server and visit
/admin/loginto authenticate as owner - Optionally set
peer-secretand configuredata/peers.kdlfor federated search across multiple instances
Prerequisites
- Go 1.23+ (the
go.modtargets 1.25 but 1.23+ should work) - SQLite3 development headers (e.g.
libsqlite3-devon Debian,sqlite-devon 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.
| Component | Lines |
|---|---|
| 4got Go source (minus tests) | 20,866 |
| 4got test code | 5,324 |
| 4got Go total | 26,190 |
| 4got KDL configs (engine defs, etc.) | 10,682 |
| 4got Python sidecar | 819 |
| 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 code | 3,764 |
| SearXNG Jinja templates | 2,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:
- Install
dnscrypt-proxyand point it at trusted DoH/DoT servers (e.g. Quad99.9.9.9, Cloudflare1.1.1.1). - Configure your local resolver (Technitium/Unbound) to forward to
127.0.0.1:5353(dnscrypt-proxy’s default listen port). - 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.