-
Notifications
You must be signed in to change notification settings - Fork 0
Setup
Silo can run as a local Docker Compose stack for evaluation and development, or on Kubernetes via Helm for production. Both paths render the same config.yaml schema, documented at the bottom of this page.
docker compose up -d
docker compose logs silo | grep -A 12 'SILO BOOTSTRAP' # grab the tokenThis brings up four services from the root docker-compose.yaml:
| service | image | purpose | ports |
|---|---|---|---|
postgres |
postgres:16-alpine |
package index, tokens, users, audit log | 5432:5432 |
seaweedfs |
chrislusf/seaweedfs:latest |
stands in for S3-compatible object storage |
8333:8333 (S3), 8888:8888 (filer web UI) |
createbucket |
chrislusf/seaweedfs:latest |
one-shot job that creates the silo bucket, then exits |
— |
silo |
built from the repo Dockerfile
|
the server |
8080:8080 (gRPC + HTTP) |
The silo service waits on postgres being healthy and createbucket having completed, and is configured with:
environment:
SILO_CONFIG: /etc/silo/config.yaml
SILO_DATABASE_URL: postgres://silo:silo@postgres:5432/silo
RUST_LOG: info
volumes:
- ./config.example.yaml:/etc/silo/config.yaml:roData persists in the named volumes postgres-data and seaweedfs-data. The credentials in this file (silo/silo, siloadmin/siloadmin) are deliberately obvious — this stack is for development only.
On the first start against an empty database, Silo mints an admin token and an admin user and prints them once; they're stored only as hashes and can't be recovered afterwards. Log in with:
silo login --server http://localhost:8080helm install silo oci://ghcr.io/birkneralex/charts/silo -f my-values.yamlThe chart refuses to render unless exactly one of these is set:
# Production: a managed instance.
externalPostgres:
existingSecret: silo-db # Secret with a `url` key# Development only: a bundled single-replica Postgres.
# No replication, no backups, no pooling.
postgres:
enabled: trueSecrets can stay out of values.yaml entirely — the server expands ${VAR} and ${VAR:-default} in its config file from the environment, so the chart writes placeholders and injects real values from Kubernetes Secrets (storage.existingSecret, auth.tokenPepperExistingSecret, signing.*.existingSecret, etc.). An unset variable with no default is a startup error rather than an empty string.
One Ingress covers both the CLI's gRPC calls and the dnf/apk/npm/pacman HTTP surface — they share a Service port, and silo tells them apart itself by path and by per-connection protocol detection. The only requirement on the ingress controller is that it speaks real HTTP/2 to the backend instead of downgrading to HTTP/1.1. Disabled by default.
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
external-dns.alpha.kubernetes.io/hostname: packages.example.com
traefik.ingress.kubernetes.io/service.serversscheme: h2c
hosts:
- host: packages.example.com
paths: [{path: /, pathType: Prefix}]
tls:
- secretName: silo-tls
hosts: [packages.example.com]Traefik is recommended — the h2c annotation above tells it to dial the backend over cleartext HTTP/2, and it forwards whatever it gets without caring whether it's gRPC or plain HTTP.
nginx can do the same, but only through plain proxy_pass with proxy_http_version 2, supported by nginx open source only since 1.29.4 (December 2025). Earlier versions can reach a backend over HTTP/2 only through the gRPC-specific grpc_pass module, which can't carry dnf/apk/npm/pacman traffic. If your ingress-nginx build doesn't expose proxy_http_version 2 through a plain annotation, force it with a snippet:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_http_version 2;commonLabels and commonAnnotations reach every resource the chart creates. Per-resource labels/annotations merge on top, for keys that only make sense on one object — e.g. IRSA or Workload Identity on the serviceAccount:
commonLabels:
team: platform
commonAnnotations:
example.com/owner: platform-team
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/silocommonLabels deliberately never touches spec.selector — that field is immutable, so folding user labels into it would break every subsequent helm upgrade.
Migrations are embedded in the binary and applied on startup. sqlx takes its own advisory lock, so every replica running them simultaneously during a rolling deploy is safe. For deployments that prefer running migrations as a separate Job:
silo-server --migrate-onlyBoth Compose (via the mounted config.example.yaml) and Helm (via the config.* values, rendered into a Secret) produce the same server config:
addr: "0.0.0.0:8080" # gRPC and HTTP share this port
public_base_url: "https://silo.example.com" # only strictly needed by npm
database:
url: "${SILO_DATABASE_URL}"
max_connections: 10
connect_timeout_seconds: 120
storage:
bucket: "silo"
region: "us-east-1"
access_key_id: "siloadmin"
secret_access_key: "siloadmin"
endpoint: "http://seaweedfs:8333" # omit for real AWS S3
allow_http: true # required for a non-TLS endpoint
auth:
bootstrap: true # mint an admin token/user on first empty-DB start
bootstrap_token_name: "bootstrap-admin"
bootstrap_username: "admin"
token_pepper: "${SILO_TOKEN_PEPPER}" # generate with `openssl rand -hex 32`
session_ttl_hours: 720
audit:
log_downloads: true
retention_days: 90
metrics:
enabled: true
require_auth: false
oidc:
issuer: "https://id.example.com"
client_id: "silo"
username_claim: "preferred_username"
scopes: ["openid", "profile"]
admin_claim: "groups"
admin_value: "silo-admins"
exclusive: false
signing:
gpg:
key_path: "/etc/silo/gpg-key.asc"
passphrase: "${SILO_GPG_PASSPHRASE}"
apk:
key_path: "/etc/silo/apk-key.pem"
key_name: "silo@example.com-1a2b3c4d.rsa.pub"
pacman:
key_path: "/etc/silo/pacman-key.asc"
passphrase: "${SILO_PACMAN_GPG_PASSPHRASE}"signing.* is optional and independent per format; omit any of them to publish unsigned in that format. See Usage for what each signing block enables client-side.
See Usage to create tokens and configure a package manager against the server you just stood up.