Clivern/Peanut: A REST API and CLI for Spinning Up Throwaway Databases
🐺 Deploy Databases and Services Easily for Development and Testing Pipelines.
At a glance
- What is it?
- Peanut wraps Docker to provision databases, brokers and observability tools on demand, with an optional deleteAfter timer. It fits test pipelines that need real services rather than mocks, but it depends on a running etcd cluster and its last tagged release is v0.7.0 from February 2023.
- Who is it for?
- Adopt Peanut if your test suite needs a real MySQL, Redis or RabbitMQ instance per run and you already operate etcd and Docker. Do not adopt it if you cannot run an etcd cluster, if you need a vendor-supported product with recent releases, or if your tests can use containers directly.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap Peanut fills between mocks and full Helm charts
Integration tests that touch a database usually fail for one of two reasons: the mock does not behave like the real engine, or the real engine is too slow and too manual to stand up per test run. Peanut targets the second problem. The README describes it as suited for "development, manual testing, automated testing pipelines where mocking is not possible and test drives", and the mechanism is straightforward: you ask for a service, it starts a container for you, and if you passed a deleteAfter value it removes that container when the timer expires.
The audience is narrower than the tag list suggests. Someone who wants a local Postgres once a week will not gain much, because docker run does that in one line. Peanut pays off when several people or several CI jobs need short-lived instances of the same set of services and need to know the host and port to connect to. The API returns that connection detail in the service listing, which is the part a raw docker command does not give you.
The README is candid about the overlap with existing tooling: the same result is achievable "with a bunch of yaml files or using a configuration management tool or a package manager like helm". The stated differentiator is size and speed of use rather than capability.
What actually happens when you POST a service
The request body carries three fields: service, configs and deleteAfter. In the README example, a POST to /api/v1/service with {"service":"redis","configs": {},"deleteAfter":"10min"} returns an object with createdAt, id, service, status and type. The status in that response is PENDING and the type is service.deploy, which tells you the call is asynchronous. Provisioning is not finished when the HTTP response arrives.
You then read state with GET /api/v1/service, which returns a services array. Each entry has its own id, the service name, a configs object containing address, password and port, the deleteAfter value and timestamps. The two entries in the README output show different addresses (127.0.0.1 and 127.0.0.2) and different ports (49156 and 49155), which is how you tell two instances of the same engine apart. One of them has an empty deleteAfter, meaning it persists.
Under the hood the README states that Peanut works with a containerization runtime such as Docker to deploy and configure the service, and destroys it when it is temporary. The config file exposes this as app.containerization.driver, with docker as the documented value. The same block has autoClean, described as cleanup of stale images, volumes and networks, and cacheTagsTimeInMinutes, which defaults to 10080 (seven days) and caches Docker image tags. State is stored through app.storage, where type is documented as local-only and path defaults to /tmp. That last detail matters: the service registry lives on the filesystem, not in a database.
Install paths: the shell script and the manual tarball
Two installation routes are documented. The fast one is a bash script: bash < <(curl -s https://raw.githubusercontent.com/Clivern/Peanut/main/deployment/linux/install.sh). The README warns it may take a while on a cold start and says the script installs etcd, docker, docker-compose and peanut. After it finishes, Peanut listens on port 80 and the UI is at http://<public-ip>. You are then told to edit /etc/peanut/config.prod.yml and set app.hostname, which defaults to ${PEANUT_API_HOSTNAME:-127.0.0.1}, to your public IP or hostname, followed by systemctl restart peanut. An upgrade script exists at deployment/linux/upgrade.sh.
The manual route downloads a release tarball named peanut_{version}_Linux_x86_64.tar.gz and requires you to install etcd separately, plus docker.io and docker-compose via apt. You then copy config.dist.yml to config.yml. The config keys visible in the README are app.mode, app.port, app.hostname, app.tls.status, app.tls.pemPath, app.tls.keyPath, app.containerization.driver, app.containerization.autoClean, app.containerization.cacheTagsTimeInMinutes, app.storage.type, app.storage.path, app.api.key and the api block. Every value is written as an environment variable with a default, so PEANUT_API_KEY, PEANUT_API_PORT and the rest can be set outside the file.
Note the etcd requirement on both paths. The README says to install an etcd cluster or a single node and refers to etcd docs or the bin directory in the repository. It does not explain what Peanut stores in etcd or what happens when etcd is unreachable, which is the largest documentation gap in the material.
Twenty-four services, and the ones the README does not cover
The supported list is long: MySQL, MariaDB, PostgreSQL, Redis, Etcd, Grafana, Elasticsearch, MongoDB, Graphite, Prometheus, Zipkin, Memcached, Mailhog, Jaeger, RabbitMQ, Consul, Vault, Cassandra, Minio, Docker Registry, Ghost, Httpbin, Nagios and Etherpad. The spread is uneven in kind. Some are datastores, some are message brokers, some are dashboards or tracing backends, and two (Ghost and Etherpad) are end-user applications.
The README gives one worked example, Redis, and no per-service documentation. There is no table of which configs keys each service accepts, no list of image tags, and no statement about which ports are exposed for each engine. The configs field in the POST body is shown as an empty object in the example, so nothing in the supplied material explains how you would, say, set a MySQL root password or pin an Elasticsearch version. That is a real cost: you will be reading the source or the dashboard to find out.
The presence of Grafana, Prometheus, Jaeger and Zipkin suggests the intended use is not only test fixtures but also short-lived observability stacks for demos. Whether that works well depends on inter-service configuration, which the README does not address.
Where Peanut is the wrong tool
The clearest failure mode is the deleteAfter timer combined with local storage. If the Peanut process restarts, the registry under app.storage.path (/tmp by default) is the only record of what was provisioned and when it should die. A container whose entry is lost becomes an orphan that no timer will clean up. The autoClean flag is described as cleaning stale images, volumes and networks, not as reconciling a lost service list, so the two are not the same safeguard.
A second boundary is the driver. Only docker is documented as a supported value for app.containerization.driver, so a Podman or containerd-only host is out of scope. The storage type is likewise documented as local-only, which rules out running several Peanut instances behind a load balancer and expecting them to share a registry.
Finally, the API is keyed by a single header, x-api-key, matching app.api.key. The README shows it in every curl example, but nothing in the material describes key rotation, per-user keys or scoping. Exposing the port 80 install to a public IP, as the quick-start instructs, puts an API that starts containers behind one shared secret. For a laptop or an internal CI runner that is acceptable. For anything reachable from the internet it deserves a second thought, and the TLS block (app.tls.status, pemPath, keyPath) defaults to off.
How it differs from docker-compose and Testcontainers
The obvious alternative is docker-compose itself. A compose file declares a fixed set of services with fixed names and ports, brought up with docker compose up and torn down with docker compose down. Peanut inverts the model: services are created one at a time through an HTTP call, get random host ports (49155 and 49156 in the README output), and can carry individual lifetimes. That is better when a test wants one Redis for ten minutes without touching the rest of the stack, and worse when you want a reproducible topology checked into git. A compose file is also declarative and reviewable; a sequence of POST calls is not.
Testcontainers takes a third position. It manages container lifecycles from inside the test process, so the test code owns startup and shutdown and no external service is needed. Peanut's approach means a CI job does not need Docker access or a language-specific library; it needs an HTTP client and an API key. The trade is a network hop and an external dependency (Peanut plus etcd plus Docker) in exchange for language neutrality. If your tests are already in a language with a mature Testcontainers binding, Peanut adds moving parts without removing any.
Release cadence, licence and what to check before adopting
The most recent tagged release in the supplied material is v0.7.0, dated 2023-02-23. The two before it, v0.6.0 and v0.5.0, are from the same day and from 2022-06-26 respectively. The repository shows a push in September 2026 and is not archived, so work continues, but there is no tagged release after v0.7.0 to point at. If you install from the release tarball you are pinning to a build that is several years old; if you build from main you are tracking unreleased code. That choice is worth making explicitly rather than by default.
Upgrade cost is low in the documented path: the upgrade.sh script and the binary tarball are the only moving parts, and configuration is a single YAML file with environment-variable overrides. The hidden cost is etcd. It is a separate distributed system that the install script sets up for you, and the README does not describe backup, quorum or failure behaviour for it. Running etcd for one developer machine is one thing; running it as a dependency of a shared CI service is another.
Peanut is MIT licensed, which permits commercial use and modification; the repository's LICENSE file is the authoritative text and this is not legal advice. The practical question is not the licence but the support model: MIT gives you no warranty and no vendor behind it. Before rolling it into a pipeline, confirm three things in your own environment: that the etcd endpoint Peanut expects is reachable and survives restarts, that app.containerization.driver is set to docker on the target host, and that the service you need appears in the supported list with configs you can actually determine from the source.
Editorial conclusion
Adopt Peanut if your test suite needs a real MySQL, Redis or RabbitMQ instance per run and you already operate etcd and Docker. Do not adopt it if you cannot run an etcd cluster, if you need a vendor-supported product with recent releases, or if your tests can use containers directly. Before committing, verify that the etcd endpoint is reachable from the Peanut host, that the containerization driver is set to docker, and that your target service appears in the supported list.
Community notes