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

  1. Shared containers run at the Rakiba root level (dev-managed-services/)
  2. Project isolation via unique databases, buckets, and ACL-restricted keys
  3. Zero configuration in projects—credentials are provisioned during bb sync
  4. 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

ServiceConfig KeyPurpose
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:

ServiceIsolation Method
PostgreSQLUnique database and user per project
DatomicUnique database name per project
ValkeyACL user restricted to project key prefix
GarageUnique bucket and access key per project
OllamaShared (models available to all projects)

For example, if you have projects billing and analytics:

  • PostgreSQL creates databases project_billing and project_analytics
  • Valkey restricts each project to keys prefixed with project_billing: and project_analytics:
  • Garage creates buckets rakiba-billing and rakiba-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>:

  1. Credentials are read from .local-envs/{project}.edn
  2. Environment variables are written to projects/{project}/rakiba.local.edn
  3. Library code is synced with config references like "env:S3_ENDPOINT"
  4. Config resolves at runtime from env vars or rakiba.local.edn fallback

Library Code

After syncing, each service provides ready-to-use code in src/rakiba/lib/:

PostgreSQL (rakiba.lib.database.*)

FilePurpose
pool.cljHikariCP connection pool (Mount state)
core.cljCRUD helpers with next-jdbc/honeysql
health.cljHealth checks

Datomic (rakiba.lib.datomic.*)

FilePurpose
peer.cljDatomic peer connection (Mount state)
migrations.cljSchema migrations via conformity
health.cljHealth checks
history.cljTime-travel query helpers

Valkey (rakiba.lib.cache.*)

FilePurpose
valkey.cljCarmine client wrapper (Mount state)
health.cljHealth checks

S3/Garage (rakiba.lib.storage.*)

FilePurpose
s3.cljCognitect aws-api S3 client (Mount state)
health.cljHealth 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.*)

FilePurpose
core.cljProvider-agnostic AI interface
ollama.cljOllama provider implementation
clippy.cljAI assistant helper
health.cljHealth checks

Email (rakiba.lib.email.*)

FilePurpose
core.cljEmail 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

ServicePortPurpose
PostgreSQL5433Database connections
Valkey6379Cache connections
Garage3900S3 API
Garage3902Web UI
Ollama11434LLM API
MailCatcher1025SMTP
MailCatcher1080Web UI

Production Deployment

In production, set actual environment variables instead of relying on rakiba.local.edn:

VariableDescription
DATABASE_URLPostgreSQL connection string
DATOMIC_URIDatomic transactor URI
VALKEY_URLValkey/Redis connection string
S3_ENDPOINTS3 API endpoint
S3_ACCESS_KEY_IDS3 access key
S3_SECRET_ACCESS_KEYS3 secret key
S3_BUCKETDefault 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:

  1. Add a new project: Run bb init newproject with the services you need
  2. Credentials auto-provision: bb sync newproject creates isolated credentials
  3. No conflicts: Each project has its own database, bucket, and cache namespace
  4. 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:

  1. This is almost always a Valkey connection issue
  2. Check project logs for “Carmine connection error”
  3. Run bb services:sync-acl to ensure ACL users are configured
  4. Restart the project server to reconnect