Managed Services
Rakiba provides shared infrastructure services for local development. Run PostgreSQL, Datomic, Valkey, S3-compatible storage, Ollama, and MailCatcher once—share them across all your projects with automatic isolation.
Why Managed Services?
Running databases and caches locally is tedious. Each project needs its own PostgreSQL, Redis, and storage setup. You end up with:
- Multiple Docker Compose files doing the same thing
- Port conflicts between projects
- Wasted disk space and memory
- Different credentials scattered everywhere
Rakiba takes a different approach: run each service once, share it across all projects.
How It Works
- Shared containers run at the Rakiba root level (
dev-managed-services/) - Project isolation via unique databases, buckets, and ACL-restricted keys
- Zero configuration in projects—credentials are provisioned during
bb sync - Library code synced into each project for immediate use
This gives you the simplicity of a monolith’s infrastructure with the isolation of separate deployments.
Available Services
| Service | Config Key | Purpose |
|---|---|---|
| PostgreSQL | :database [:sql] | Relational database with HikariCP pooling |
| Datomic Pro | :database [:datomic-pro] | Immutable Datalog database with time-travel |
| Valkey | :cache [:valkey] | Redis-compatible cache for sessions and data |
| Garage (S3) | :storage [:s3] | S3-compatible object storage |
| Ollama | :ai-providers [:ollama] | Local LLM inference |
| MailCatcher | :auth [:email-password] | Development email capture |
Enabling Services
Add the services you need to your project’s rakiba.edn:
{:database [:sql :datomic-pro] ; PostgreSQL and/or Datomic
:storage [:s3] ; S3-compatible storage (Garage)
:cache [:valkey] ; Valkey cache
:ai-providers [:ollama] ; Local LLM
:ai-models ["gemma3:1b"]} ; Models to pull for Ollama
Then sync from the Rakiba root:
bb sync <project>
This provisions credentials and syncs the library code into your project.
Project Isolation
Each project gets isolated resources within the shared services:
| Service | Isolation Method |
|---|---|
| PostgreSQL | Unique database and user per project |
| Datomic | Unique database name per project |
| Valkey | ACL user restricted to project key prefix |
| Garage | Unique bucket and access key per project |
| Ollama | Shared (models available to all projects) |
For example, if you have projects billing and analytics:
- PostgreSQL creates databases
project_billingandproject_analytics - Valkey restricts each project to keys prefixed with
project_billing:andproject_analytics: - Garage creates buckets
rakiba-billingandrakiba-analytics
Projects cannot access each other’s data, even though they share the same containers.
Credential Architecture
Source of Truth
All project credentials live in .local-envs/{project}.edn at the Rakiba root:
;; .local-envs/myproject.edn
{:db {:database "project_myproject"
:user "project_myproject"
:password "..."
:host "localhost"
:port 5433}
:storage {:bucket-name "rakiba-myproject"
:access-key-id "GK..."
:secret-access-key "..."
:endpoint "http://localhost:3900"}
:cache {:acl-user "project_myproject"
:password "..."
:key-prefix "project_myproject:"
:endpoint "localhost:6379"}}
Credential Flow
When you run bb sync <project>:
- Credentials are read from
.local-envs/{project}.edn - Environment variables are written to
projects/{project}/rakiba.local.edn - Library code is synced with config references like
"env:S3_ENDPOINT" - Config resolves at runtime from env vars or
rakiba.local.ednfallback
Library Code
After syncing, each service provides ready-to-use code in src/rakiba/lib/:
PostgreSQL (rakiba.lib.database.*)
| File | Purpose |
|---|---|
pool.clj | HikariCP connection pool (Mount state) |
core.clj | CRUD helpers with next-jdbc/honeysql |
health.clj | Health checks |
Datomic (rakiba.lib.datomic.*)
| File | Purpose |
|---|---|
peer.clj | Datomic peer connection (Mount state) |
migrations.clj | Schema migrations via conformity |
health.clj | Health checks |
history.clj | Time-travel query helpers |
Valkey (rakiba.lib.cache.*)
| File | Purpose |
|---|---|
valkey.clj | Carmine client wrapper (Mount state) |
health.clj | Health checks |
S3/Garage (rakiba.lib.storage.*)
| File | Purpose |
|---|---|
s3.clj | Cognitect aws-api S3 client (Mount state) |
health.clj | Health checks |
Usage example:
(ns myapp.storage
(:require [mount.core :as mount]
[rakiba.lib.storage.s3 :as s3]))
;; Start the S3 client
(mount/start #'s3/s3-client)
;; Basic operations
(s3/put-object! (s3/client) (s3/default-bucket) "key.txt" "content")
(s3/get-object-string (s3/client) (s3/default-bucket) "key.txt")
;; => {:success true :content "content" ...}
(s3/list-objects (s3/client) (s3/default-bucket) "prefix/")
;; => {:success true :objects [{:key "prefix/file1.txt" :size 123 ...}]}
(s3/delete-object! (s3/client) (s3/default-bucket) "key.txt")
Ollama (rakiba.lib.ai.*)
| File | Purpose |
|---|---|
core.clj | Provider-agnostic AI interface |
ollama.clj | Ollama provider implementation |
clippy.clj | AI assistant helper |
health.clj | Health checks |
Email (rakiba.lib.email.*)
| File | Purpose |
|---|---|
core.clj | Email sending via postal |
MailCatcher captures all outgoing emails at localhost:1080 (web UI) and accepts SMTP on port 1025.
Running Services
Start services before running your projects:
# From Rakiba root
bb services:up # Start all managed services
bb services:down # Stop all services
bb services:logs # Tail logs
bb services:setup # First-time setup (creates credentials, buckets, etc.)
bb services:sync-acl # Sync Valkey ACL users from .local-envs
Service Ports
| Service | Port | Purpose |
|---|---|---|
| PostgreSQL | 5433 | Database connections |
| Valkey | 6379 | Cache connections |
| Garage | 3900 | S3 API |
| Garage | 3902 | Web UI |
| Ollama | 11434 | LLM API |
| MailCatcher | 1025 | SMTP |
| MailCatcher | 1080 | Web UI |
Production Deployment
In production, set actual environment variables instead of relying on rakiba.local.edn:
| Variable | Description |
|---|---|
DATABASE_URL | PostgreSQL connection string |
DATOMIC_URI | Datomic transactor URI |
VALKEY_URL | Valkey/Redis connection string |
S3_ENDPOINT | S3 API endpoint |
S3_ACCESS_KEY_ID | S3 access key |
S3_SECRET_ACCESS_KEY | S3 secret key |
S3_BUCKET | Default bucket name |
The library code automatically reads from environment variables, so no code changes are needed between development and production.
Sharing Across Projects
The managed services architecture lets multiple teams or projects share infrastructure without stepping on each other:
- Add a new project: Run
bb init newprojectwith the services you need - Credentials auto-provision:
bb sync newprojectcreates isolated credentials - No conflicts: Each project has its own database, bucket, and cache namespace
- Single source of truth: All credentials stored in
.local-envs/at Rakiba root
This is particularly useful for:
- Microservices: Multiple services sharing a database cluster
- Feature branches: Test branches with isolated data
- Team development: Multiple developers with separate local data
- CI/CD: Consistent infrastructure across environments
Troubleshooting
Port Conflicts
If bb services:up fails with “port already allocated”:
# Find what's using the port
lsof -i :<port>
# Or check Docker containers
docker ps --format '{{.Names}} {{.Ports}}' | grep <port>
Valkey Authentication Errors
If you get WRONGPASS invalid username-password pair:
# Regenerate ACL file and reload Valkey
bb services:sync-acl
# Verify ACL user was created
docker exec rakiba-valkey valkey-cli ACL LIST | grep project_myproject
This usually happens after container recreation, which loses in-memory ACL users.
Garage Connection Refused
If S3 operations return Connection refused:
# Check Garage is running
docker ps | grep garage
# Check ports are bound
docker port rakiba-garage
# Restart if needed
cd dev-managed-services && docker compose up -d garage
Invalid S3 Credentials
If S3 operations return 403 Forbidden:
# Verify bucket exists
docker exec rakiba-garage /garage bucket list
# Verify key has permissions
docker exec rakiba-garage /garage key info <key-name>
# Recreate if needed
docker exec rakiba-garage /garage bucket create myproject
docker exec rakiba-garage /garage key create myproject
docker exec rakiba-garage /garage bucket allow --read --write myproject --key myproject
Sessions Not Persisting
If login succeeds but subsequent requests are 401:
- This is almost always a Valkey connection issue
- Check project logs for “Carmine connection error”
- Run
bb services:sync-aclto ensure ACL users are configured - Restart the project server to reconnect