# degoog, a search aggregator whose extension system is also its security model

> degoog queries several search engines, merges the results, and lets you add your own engines, bang commands, slot panels and fetch transports, which is the feature that makes it flexible and the reason a password gate matters. It runs on port 4444, ships as a container image with a NixOS module beside it, and is published under AGPL-3.0 in a stable beta line.

**degoog-org/degoog** — Search engine aggregator with a comprehensive plugin/extension system

- Repository: https://github.com/degoog-org/degoog
- Website: https://degoog-org.github.io/docs/
- Stars: 2,173 · Forks: 100
- Language: TypeScript
- License: AGPL-3.0
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/degoog-org-degoog

## What degoog aggregates, and what it hands over to plugins

degoog does one thing at its core: it queries multiple search engines and shows the results in one place. The project's own description of the surrounding design is more revealing than that, because it enumerates four extension points. You can add custom search engines. You can add bang-command plugins, the convention where typing an exclamation mark followed by a word sends the query somewhere else. You can add slot plugins, which are query-triggered panels that appear above or below the results or in the sidebar. And you can add transports, which are custom HTTP fetch strategies, with curl, FlareSolverr and your own implementation given as examples.

That last one is the interesting category, and it explains the rest of the design. Search engines return HTML, block default clients, and change their markup without notice. A transport is the layer that decides how a request is actually made, which means a user can swap in a browser-impersonating client, a challenge-solving proxy, or a hand-written fetcher, without touching the engine definitions.

The stated ambition is a user-made marketplace for plugins and engines, and there is already a community extension repository alongside the official store. The author's framing of the project's identity is also clear in the README: it exists because the searxng developers had the idea first, and degoog is a take on a heavily customisable search aggregator meant to be a more modular and lighter alternative, with a core that stays as simple as possible and everything else arriving as plugins.

So the audience is self-hosters who have outgrown a default search engine and want to own the query path. The state is stable beta, and the README says you can use it in production but that there may be some inconsistent behaviour.

## DEGOOG_SETTINGS_PASSWORDS is the security boundary, and the README says so

Every deployment example in this README carries the same comment, and it is the most important line in the document: set `DEGOOG_SETTINGS_PASSWORDS` before exposing the instance to the internet, because an unlocked instance lets anyone install extensions, which runs code on the server.

Read that as a design statement rather than a warning about a bug. Extensions in this project are not declarative configuration. A transport can be arbitrary HTTP behaviour, a bang plugin can be arbitrary logic, and a slot plugin renders into the results page. The permission model is therefore all or nothing, and it is enforced by a settings gate whose credentials live in a file the environment points at as `DEGOOG_SETTINGS_TOKENS_FILE`, defaulting to `./data/settings-tokens.json`.

That has three practical consequences. First, there is no partial trust: there is no read-only extension and no sandboxed extension tier, so the decision to install something is the decision to run someone else's code inside your process. Second, on a shared or public instance the gate is the only thing between a stranger and that capability, which is why the Quadlet example carries an `DEGOOG_PUBLIC_INSTANCE=true` line to add when the instance is public. Third, the default compose file does not set the variable at all, so a deployment that copies `docker-compose.yml` verbatim is unlocked on whatever interface it binds.

The related warning about the community store follows the same logic. The README says those extensions have only been initially vetted, that there is no way for the author to keep an eye on them once added, and that it is your own responsibility to make sure what you install is safe. Combined with the extension model, that is the correct way to read that store: as a source of code, not of features.

## Four directories under ./data, and why the state layout tells you how it works

The example environment file is the clearest description of the extension model, because it gives every kind of extension its own directory inside a single data folder:

```bash
DEGOOG_ENGINES_DIR=./data/engines
DEGOOG_TRANSPORTS_DIR=./data/transports
DEGOOG_SETTINGS_TOKENS_FILE=./data/settings-tokens.json
```

The full list runs to ten variables: engines, plugins, themes, transports and autocomplete are directories, while aliases, plugin settings, the default engine set, the settings tokens and the blocklist are files. So a degoog installation is one directory you can back up, and everything the project considers mutable lives inside it.

Two properties follow from that layout. The first is that the extension system is filesystem-based rather than package-manager-based. There is no `npm install` for a degoog plugin and no version pinning; a plugin is a directory, and whatever it contains is what runs. That is what makes the store possible and what makes the trust question unavoidable.

The second is that the data directory is the only thing that survives an upgrade. The container image is disposable, the volume is not, and the port and the credentials both come from the environment rather than from anything inside the image. That is a clean separation and it is the reason the README's first instruction is about creating and chowning `./data` before anything else runs:

```bash
mkdir -p ./data
sudo chown -R 1000:1000 ./data
```

The ownership matters because the image runs as uid 1000 by default. Get it wrong and the container starts with a volume it cannot write, which surfaces as a first-run failure rather than as data loss.

## Port 4444, a /readyz healthcheck, and six ways to run it

The default listener is port 4444, and the image runs as user 1000:1000. The minimal Docker invocation from the README is one line:

```bash
docker run -d --name degoog -p 4444:4444 -v ./data:/app/data -e DEGOOG_SETTINGS_PASSWORDS=changeme --restart unless-stopped ghcr.io/degoog-org/degoog:latest
```

The bundled compose file is the same idea with the credentials omitted, and the comment in it is pointed about that: the more you add, the heavier the instance gets, and the real configurations live in a directory of examples rather than in the top-level file.

Those examples are worth reading as a scaling guide rather than as boilerplate. `simple.yml` runs degoog alone for personal use at low traffic. `valkey.yml` adds Valkey, described as what you want for a multi-replica or public instance because a shared cache keeps settings and invalidation in sync across replicas. `postgres.yml` adds Postgres for a busy public instance with a large indexer, on the grounds that Postgres scales concurrent writes and full-text search better than SQLite. `full.yml` is both. And `mcp.yml` runs degoog next to a separate MCP server so the same instance can be queried by LLM clients, with Claude, Cursor and llama.cpp named as examples.

Health checking is built into the image, and the endpoint it uses tells you what the service considers ready:

```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD curl -fsS "http://127.0.0.1:${DEGOOG_PORT:-4444}/readyz"
```

The forty second start period is a deliberate allowance for a cold start on a slow indexer, and the check is a loopback request, so it does not require anything to be exposed.

Beyond containers there are three more paths. A Podman Quadlet unit is included for systemd hosts, a Nix flake provides both a package and a NixOS module with options such as `configurePostgres` and a `DEGOOG_UNIX_SOCKET` environment variable, and a native run needs bun, git and curl. The native sequence is four commands, and the README's copy of it has a stray colon at the start of the second line:

```bash
git clone https://github.com/degoog-org/degoog.git
cd degoog
bun install
bun run build
bun run start
```

There is also a community Proxmox VE script, and the README marks it as in development and not recommended for production use, which is more candour than most projects manage about their community integrations.

## What the release image installs, and why python3 and curl impersonation are in there

The Dockerfile is a four-stage build on `oven/bun:1.3.14-alpine`, and the final stage is where the interesting decisions are. Alongside copying the built application and `node_modules` from earlier stages, it installs a specific set of system packages: git, ca-certificates, su-exec, curl, bash, python3, py3-lxml, py3-babel and py3-dateutil. It then runs `scripts/install-curl-impersonate.sh` and deletes the script.

Those packages are not a generic Alpine wish list. The Python stack with lxml, babel and dateutil is a set of HTML and locale parsing tools, and the curl impersonation script is the reason a container that looks like a Bun web app also needs curl. Together they are the physical implementation of the transports concept: a user can drop a transport into the transports directory that shells out to a client presenting a browser-like TLS and HTTP fingerprint, and the image is pre-provisioned to make that possible.

That is a coherent design, and it is also the project's main operational dependency that documentation elsewhere would need to explain. A transport that depends on curl impersonation is a transport that depends on the operating system underneath, so a user writing one against a different base image, or a maintainer slimming the image for a smaller footprint, breaks user extensions without touching degoog's own code. Anyone customising this image should treat that script and its package list as part of the public contract.

The rest of the runtime configuration is conventional and well made. `DEGOOG_PORT` defaults to 4444, the port is exposed, and the entrypoint is a script rather than a bare command, which is where the uid and gid handling lives. The application dependencies are pinned to exact versions in the manifest rather than ranges, from cheerio and hono through ioredis and the postgres driver, and there is a `resolutions` block pinning five transitive packages, which is the pattern you see when transitive advisories have been dealt with by force.

## degoog against searxng, and against a browser with three engines set up

The project names its own ancestry, and taking that at face value makes the comparison easier.

The README credits the searxng developers with the original idea and positions degoog as a more modular and lighter alternative with a core that stays simple and everything else arriving as plugins. The difference in approach is therefore not the metasearch concept, which is shared, but the extension contract. In a metasearch you are choosing which instances and which engines to query and you are accepting the engine definitions you are given. In degoog the engine, the transport, the bang command, the panel and the theme are all files you can write, and the transport layer in particular is not something a conventional aggregator exposes.

That flexibility is the whole product and also the whole cost. There is no package manager, no pinning and no permission model finer than the settings gate, so the operational burden of degoog is that you now maintain a directory of code you did not write. For a single user on a home server with a handful of engines, that is a good trade. For a team, it is a supply chain you have taken on.

The second alternative is not a project at all: three search engines configured in one browser, or a browser extension that does the same job with none of the deployment. The difference is where the queries go and who sees them. degoog keeps the history on your own hardware, lets you add a source nobody else runs, and gives you a JSON document per source to do something else with. What it costs is the maintenance, the gate, and the fact that a scraper-based aggregator breaks whenever an engine changes its markup, which is the failure mode every project in this space shares and none of them fully solves.

## AGPL-3.0, fortnightly betas, and a compose file with a sponsor name in it

The licence is the GNU Affero General Public License version 3, with the text in the `LICENSE` file. That is the copyleft variant with the network clause, so if you modify degoog and let users interact with your modified version over a network, you owe those users the corresponding source. For a self-hosted aggregator that you run for yourself, the obligation is largely theoretical; for anyone building a hosted degoog service on top of this code, it is the first thing to read. This is a description of the licence terms rather than legal advice.

The release pattern is the healthiest part of the project. Three tagged releases appear in the recent list, 0.24.0 on 2026-08-08, 0.25.0 on 2026-08-30 and 0.26.0 on 2026-09-13, each labelled Stable Beta, which is roughly a fortnightly cadence, and the manifest version matches the newest tag at 0.26.0. The last push was on 2026-09-28 and the repository is not archived. Note the shape of that: still below 1.0, so a minor bump inside 0.26 can change behaviour, and the README's own warning about some inconsistent behaviour is the honest companion to that version number.

The engineering artefacts around the code are unusually complete for a 0.x project. There is a `test` script that discovers every `*.test.ts` under `tests/` and deliberately excludes the `stress/` directory, a separate `test:rate-limit` script that runs only the stress tests with debug logging, `typecheck` and `lint` scripts, and an `AGENTS.md`, a `CLAUDE.md` and a `.coderabbit/` directory at the top level, which together suggest the repository is routinely modified by coding agents and has been set up for it.

One small inconsistency is worth flagging because it tells you how the repository is assembled. The bundled `docker-compose.yml` opens with a comment addressed to a specific sponsor handle and a buymeacoffee link in the README carries the same handle, so at least part of this tree has been maintained through a fork that upstream inherited. It is harmless, and it is also a reminder to read the compose file rather than assume it matches the upstream documentation.

## Conclusion

Adopt degoog if you want a self-hosted metasearch you can extend with your own engines and transports, and you are prepared to treat installed extensions as trusted code. Do not put it on the internet without setting DEGOOG_SETTINGS_PASSWORDS, because the README states that an unlocked instance lets anyone install extensions and that extensions run code on the server. Verify first by running the simple compose file, checking that /readyz returns healthy through the image healthcheck, and reading the settings gate documentation before you install anything from the community extension store, which the author says is only initially vetted.

## FAQ

### How do I secure a public degoog instance?

Set DEGOOG_SETTINGS_PASSWORDS before exposing the instance, which the README repeats in every deployment example. The reason is that an unlocked instance lets anyone install extensions, and extensions run code on the server. The Quadlet example also shows a DEGOOG_PUBLIC_INSTANCE=true line to add for public deployments.

### Which port does degoog use, and where does it keep its data?

The app runs on port 4444 by default as user 1000:1000, and everything it stores lives under a data directory you create and chown to that user before the first run. The container image is disposable and the mounted data directory is what survives upgrades.

### What kinds of extensions does degoog support?

Custom search engines, bang-command plugins, slot plugins that appear as query-triggered panels above or below results or in the sidebar, transports that are custom HTTP fetch strategies such as curl, FlareSolverr or your own, and themes. Each kind has its own directory under the data folder, configured through variables like DEGOOG_ENGINES_DIR and DEGOOG_TRANSPORTS_DIR.

### Can I run degoog without Docker?

Yes. The native path needs bun, git and curl, then a clone, bun install, bun run build and bun run start. There is also a Nix flake with a package and a NixOS module, and a Podman Quadlet unit for systemd hosts.

### Which databases can a degoog instance use?

SQLite by default, with Valkey and Postgres available through the compose examples. Valkey is described as a shared cache for multi-replica or public instances so settings and invalidation stay in sync, and Postgres is recommended for a busy public instance with a large indexer because it scales concurrent writes and full-text search better than SQLite.

### Can an LLM or MCP client query degoog?

Yes, through a separate companion project. One of the compose examples, mcp.yml, runs degoog alongside degoog-mcp so the instance can be exposed to LLM and MCP clients, with Claude, Cursor and llama.cpp named as examples in the README.

## Sources

- [degoog-org/degoog on GitHub](https://github.com/degoog-org/degoog)
- [License: AGPL-3.0](https://github.com/degoog-org/degoog/blob/main/LICENSE)
- [Project website](https://degoog-org.github.io/docs/)
- [README](https://github.com/degoog-org/degoog/blob/main/README.md)
- [Releases](https://github.com/degoog-org/degoog/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/degoog-org-degoog
