One binary. Zero orchestra.
The API, web interface and CLI ship together, with state in a single SQLite database. Deploy it next to your app. No external storage to run.
Yily is a fast, self-hosted home for your API keys, tokens and passwords. One Go binary, one SQLite file, AES-256-GCM at rest. None of the ceremony.
Yily started with a question: why does secret management need so much machinery?
The secret managers I tried were built for large teams. They came with clusters, agents, external databases, and a bill that grows with every environment. I wanted the opposite: one secure place for my keys, a deploy that takes a minute, and a footprint that costs little to run.
That constraint shaped the architecture: one Go binary, one SQLite file, one master key. Everything else had to earn its place.
The API, web interface and CLI ship together, with state in a single SQLite database. Deploy it next to your app. No external storage to run.
Every value is sealed with AES-256-GCM and a fresh nonce. The master key never enters the database.
Every value change becomes a version. Reactivate an older one the moment a deploy goes sideways.
Read- or write-scoped tokens, SHA-256 hashed before storage, revocable in one click. The raw token is shown exactly once.
Sign-ins, rotations and revocations are recorded with the actor, target and time. Export the trail when you need proof.
Yily was built around one job: getting a value from encrypted storage into your process without leaking it on the way. Here is every hop.
The REST API is live today. Thin, read-only SDKs are next: Go first, then Rust, Java, JavaScript and Python. Same single request, native types everywhere.
type AppConfig struct {
DBHost string `yily:"DB_HOST"`
DBPort int `yily:"DB_PORT"`
}
func main() {
client, err := yily.NewClient(yily.Config{
BaseURL: "http://localhost:16529",
EnvironmentID: "your-environment-uuid",
Token: os.Getenv("YILY_TOKEN"),
})
if err != nil {
log.Fatal(err)
}
var cfg AppConfig
if err := client.Load(context.Background(), &cfg); err != nil {
log.Fatal(err)
}
log.Printf("db: %s:%d", cfg.DBHost, cfg.DBPort)
}// Coming soon. This SDK is not implemented yet.
// We are looking for a contributor with Rust experience to build it.
// See CONTRIBUTING.md to get started.// Coming soon. This SDK is not implemented yet.
// We are looking for a contributor with Java experience to build it.
// See CONTRIBUTING.md to get started.// Coming soon. This SDK is not implemented yet.
// We are looking for a contributor with JavaScript experience to build it.
// See CONTRIBUTING.md to get started.# Coming soon. This SDK is not implemented yet.
# We are looking for a contributor with Python experience to build it.
# See CONTRIBUTING.md to get started.Need it before the SDKs land? The batched read endpoint is available now, so you can use the REST API today .
Standard primitives, explicit context, zero cleverness. Secrets are encrypted before they touch SQLite; tokens are hashed before they are stored.
Authenticated encryption with a fresh 96-bit nonce for every write and a 128-bit tag.
AEAD · hardware acceleratedCiphertext is bound to its variable ID and version. Swap it in SQLite and decryption fails closed.
yily:<variable>:<version>Memory-hard hashing at the OWASP baseline, with a unique salt for every credential.
t=3 · m=64 MB · p=4Sessions and personal access tokens are stored only as digests. Raw tokens are shown once.
hex(sha256(token))Absolute and idle expiry. HttpOnly cookies, SameSite=Lax, and Secure whenever HTTPS is detected.
HttpOnly · Lax · SecureSelf-contained authentication. No OAuth provider, no SMTP server, no third party in the loop.
single-tenant by designThe stored format is versioned, self-describing and context-bound. It is impossible to move a ciphertext to another variable or version without authentication failing.
Zero plaintext at rest. If the master key is lost, the ciphertext stays closed. Read the spec .
Generate one master key, start one container and open the dashboard. The API, the web interface and the data all stay on your infrastructure.
Guard the master key. If it is lost, the encrypted values are unrecoverable. That is by design. Store it outside the database and back it up.
echo "YILY_MASTER_KEY=$(openssl rand -base64 32)" > yily.env
chmod 600 yily.envdocker run -d --name yily \
--restart unless-stopped \
-p 16529:16529 \
-v yily-data:/data \
--env-file yily.env \
ghcr.io/yannick2009/yily:1.0.0-beta.1services:
yily:
image: ghcr.io/yannick2009/yily:1.0.0-beta.1
restart: unless-stopped
ports:
- "16529:16529"
volumes:
- yily-data:/data
env_file:
- yily.env
volumes:
yily-data:docker compose up -dcurl -LO https://github.com/yannick2009/yily/releases/download/v1.0.0-beta.1/yily_1.0.0-beta.1_linux_amd64.tar.gz
tar -xzf yily_1.0.0-beta.1_linux_amd64.tar.gz
set -a; source ./yily.env; set +a # exports YILY_MASTER_KEY
./yily_1.0.0-beta.1_linux_amd64/yilyThe current image is linux/amd64. On ARM, run it with--platform linux/amd64 until a native build ships.
Audit every line, fork it if you disagree, and run it for as long as you like. No seats, no license ceiling, and no vendor holding the keys.
A small group of people keeps Yily moving, and there is always room for one more. Fix a bug, polish a screen, or just tell us what feels broken.
Still curious? The source is public, the specs are in the repo, and the issue tracker is open.
It is in public beta (v1.0.0-beta.1). The core works and is self-hostable today, but pin a release, back up the data volume and master key, and expect APIs to evolve. Read the release notes before upgrading.
Yily is deliberately small: one Go binary, SQLite, one admin and no external dependencies. If you need distributed clusters, dynamic secrets or multi-tenant RBAC, those tools are a better fit. If you want a vault you can understand in an afternoon, Yily is for you.
The encrypted values become unrecoverable. There is no recovery path, by design. Generate the key once, store it outside the database and back it up somewhere safe.
The REST API and CLI are available now. Go, Rust, Java, JavaScript and Python SDKs are on the roadmap, with Go first. The snippets on this page are design previews and may change before release.
The current image is linux/amd64 only. On ARM hosts, Docker can emulate it by adding --platform linux/amd64. A native ARM build is not available yet.
Open an issue, send a pull request or improve the docs. The contributing guide covers the workflow. Bug reports and documentation fixes count just as much as code.