Skip to content

Systemd Ansible Deployment

ContextUnity defines a 100% native systemd deployment pipeline managed neutrally and idempotently via Ansible configuration management. Containers are strictly deprecated for operational service runtimes inside production deployments, ensuring host reliability and bare-metal speed native memory management.

Declarative Pipeline

  1. Host Environment Prep: The infrastructure/ansible role bootstraps your host (LXC, VM, bare metal), installing system dependencies such as uv, Git, and core service components (PostgreSQL, Redis, Temporal).
  2. ContextUnity Sourcing: The codebase is synced to /opt/contextunity/ using rsync or git.
  3. Dependency Isolation: The role sets up a separate virtual environment (.venv) for packages/core and for each service inside /opt/contextunity/{svc}/ and installs packages via uv pip install.
  4. YAML Config Rendering: Jinja2 templates ({service}.yml.j2) are rendered to the service working directory (/opt/contextunity/{service}/{service}.yml). Secret keys are strictly kept out of this layer.
  5. Encrypted Credentials provisioning: Non-plaintext secrets are encrypted at deploy time via systemd-creds and stored in /etc/credstore.encrypted/.
  6. Service Life Cycle: Service unit descriptors are deployed to /etc/systemd/system/contextunity-{svc}.service and enabled/restarted.

Systemd Process Orchestration

ContextUnity services execute directly within their respective virtual environments:

[Service]
Type=simple
User=contextunity
Group=contextunity
WorkingDirectory=/opt/contextunity/brain
EnvironmentFile=/opt/contextunity/brain/.env
ExecStart=/opt/contextunity/brain/.venv/bin/contextbrain serve
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
  • Graceful Shutdown: Every service handles SIGTERM cleanly (via graceful_shutdown() async handlers) and shuts down gracefully within the TimeoutStopSec limit.
  • Logging: Standard output and error are piped to journald for central telemetry collection and monitoring.

Systemd Credentials (Secret Hardening)

ContextUnity utilizes systemd-creds for encrypting secrets at rest. Plaintext secrets are strictly prohibited in configuration files or .env files in production. Instead, secrets are encrypted by Ansible during deployment to /etc/credstore.encrypted/ and decrypted dynamically at service startup by systemd into memory-only paths under CREDENTIALS_DIRECTORY.

How it works

  1. Ansible provisions encrypted credentials during deployment:

    # In cu_service role — deploy_one.yml
    - name: Provision encrypted systemd credentials
    shell: |
    printf '%s' '{{ item.value }}' | systemd-creds encrypt \
    --name={{ item.name }} - \
    /etc/credstore.encrypted/{{ svc }}_{{ item.name }}
    loop: "{{ svc_cfg.credentials | default([]) }}"
    no_log: true
  2. Systemd unit loads credentials at service start:

    # In service.j2 template
    LoadCredentialEncrypted=shield_master_key:/etc/credstore.encrypted/shield_shield_master_key
  3. Automatic Resolution: When loading configs, the core config factory automatically retrieves mapped keys (e.g. shield_master_key) via _read_credential(), which checks the systemd path first:

    # In contextunity.core.config.env:
    # Checks $CREDENTIALS_DIRECTORY/shield_master_key first, falls back to env var SHIELD_MASTER_KEY
    secret = _read_credential("shield_master_key")

Managed Secrets Inventory

ServiceCredential NameEnv Fallback VariablePurpose
Shieldshield_master_keySHIELD_MASTER_KEYAuthority master key
Shieldshield_encryption_keySHIELD_ENCRYPTION_KEYPayload database encryptor
Routeropenai_api_keyOPENAI_API_KEYOpenAI API access
Routerinception_api_keyINCEPTION_API_KEYInception API access
Routerlangfuse_secret_keyLANGFUSE_SECRET_KEYLangfuse monitoring key
Brainbrain_database_urlBRAIN_DATABASE_URLPostgreSQL connection string
Viewdjango_secret_keyDJANGO_SECRET_KEYDjango session signing
Allredis_secret_keyREDIS_SECRET_KEYRedis payload encryptor
Allcu_project_secretCU_PROJECT_SECRETContextToken signature secret

Ansible Configuration

Secrets are defined securely inside inventory groups (using Ansible Vault for encryption) and mapped inside cu_service_config in group_vars/all/vars.yml:

cu_service_config:
shield:
description: "ContextShield -- Security Authority"
exec: "contextshield serve --port {{ service_ports.shield }}"
port: "{{ service_ports.shield }}"
env_template: shield.env.j2
config_template: shield.yml.j2
credentials:
- { name: shield_master_key, value: "{{ shield_master_key }}" }
- { name: shield_encryption_key, value: "{{ vault_shield_encryption_key }}" }
- { name: redis_secret_key, value: "{{ redis_secret_key }}" }
- { name: cu_project_secret, value: "{{ view_project_secret }}" }

Security Hardening

All deployed service units enforce process isolation and hardening guidelines:

[Service]
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/tmp /var/log/contextunity /opt/contextunity/core
PrivateTmp=true

Combined with systemd-creds, this guarantees that:

  • Secrets are encrypted at rest on disk inside /etc/credstore.encrypted/ (with strict 0700 root permissions).
  • Secrets are decrypted only in memory by the systemd service supervisor.
  • Secrets are never exposed in plaintext .env files, logs, or process environment tables.