# Service Integration Guide This guide explains how to add new containerized services to rick-infra with PostgreSQL and Valkey/Redis access via Unix sockets. ## Overview Rick-infra provides a standardized approach for containerized services to access infrastructure services through Unix sockets, maintaining security while providing optimal performance. ## Architecture Pattern ``` ┌─────────────────────────────────────────────────────────────┐ │ Application Service (Podman Container) │ │ │ │ ┌─────────────────┐ │ │ │ Your Container │ │ │ │ UID: service │ (host user namespace) │ │ │ Groups: service,│ │ │ │ postgres, │ (supplementary groups preserved) │ │ │ valkey │ │ │ └─────────────────┘ │ │ │ │ │ └─────────────────────┐ │ └─────────────────────────────────│───────────────────────────┘ │ ┌───────────────▼──────────────┐ │ Host Infrastructure Services │ │ │ │ PostgreSQL Unix Socket │ │ /var/run/postgresql/ │ │ │ │ Valkey Unix Socket │ │ /var/run/valkey/ │ └──────────────────────────────┘ ``` ## Prerequisites Your service must be deployed as: 1. **Systemd user service** (via Quadlet) 2. **Dedicated system user** 3. **Podman container** (rootless) ## Step 1: User Setup Create a dedicated system user for your service and add it to infrastructure groups: ```yaml - name: Create service user user: name: myservice system: true shell: /bin/false home: /opt/myservice create_home: true - name: Add service user to infrastructure groups user: name: myservice groups: - postgres # For PostgreSQL access - valkey # For Valkey/Redis access append: true ``` ## Step 2: Container Configuration ### Pod Configuration (`myservice.pod`) ```ini [Unit] Description=My Service Pod [Pod] PublishPort=127.0.0.1:8080:8080 PodmanArgs=--userns=host [Service] Restart=always TimeoutStartSec=900 [Install] WantedBy=default.target ``` **Key Points**: - `--userns=host` preserves host user namespace - Standard port publishing for network access ### Container Configuration (`myservice.container`) ```ini [Unit] Description=My Service Container [Container] Image=my-service:latest Pod=myservice.pod EnvironmentFile=/opt/myservice/.env User={{ service_uid }}:{{ service_gid }} Annotation=run.oci.keep_original_groups=1 # Volume mounts for sockets Volume=/var/run/postgresql:/var/run/postgresql:Z Volume=/var/run/valkey:/var/run/valkey:Z # Application volumes Volume=/opt/myservice/data:/data Volume=/opt/myservice/logs:/logs Exec=my-service [Service] Restart=always [Install] WantedBy=default.target ``` **Key Points**: - `Annotation=run.oci.keep_original_groups=1` preserves supplementary groups - Mount socket directories with `:Z` for SELinux relabeling - Use host UID/GID for the service user ## Step 3: Service Configuration ### PostgreSQL Connection Use Unix socket connection strings: ```bash # Environment variable DATABASE_URL=postgresql://myservice@/myservice_db?host=/var/run/postgresql # Or separate variables DB_HOST=/var/run/postgresql DB_USER=myservice DB_NAME=myservice_db # No DB_PORT needed for Unix sockets ``` ### Valkey/Redis Connection **Correct Format** (avoids URL parsing issues): ```bash # Single URL format (recommended) CACHE_URL=unix:///var/run/valkey/valkey.sock?db=2&password=your_password # Alternative format REDIS_URL=redis://localhost/2?unix_socket_path=/var/run/valkey/valkey.sock ``` **Avoid** separate HOST/DB variables which can cause port parsing issues: ```bash # DON'T USE - causes parsing problems REDIS_HOST=unix:///var/run/valkey/valkey.sock REDIS_DB=2 ``` ## Step 4: Database Setup Add database setup tasks to your role: ```yaml - name: Create application database postgresql_db: name: "{{ service_db_name }}" owner: "{{ service_db_user }}" encoding: UTF-8 lc_collate: en_US.UTF-8 lc_ctype: en_US.UTF-8 become_user: postgres - name: Create application database user postgresql_user: name: "{{ service_db_user }}" password: "{{ service_db_password }}" db: "{{ service_db_name }}" priv: ALL become_user: postgres - name: Grant connect privileges postgresql_privs: db: "{{ service_db_name }}" role: "{{ service_db_user }}" objs: ALL_IN_SCHEMA privs: ALL become_user: postgres ``` ## Step 5: Service Role Template Create an Ansible role using this pattern: ``` myservice/ ├── defaults/main.yml ├── handlers/main.yml ├── tasks/ │ ├── main.yml │ ├── database.yml │ └── cache.yml ├── templates/ │ ├── myservice.env.j2 │ ├── myservice.pod │ ├── myservice.container │ └── myservice.caddy.j2 └── README.md ``` ### Example Environment Template ```bash # My Service Configuration # Generated by Ansible - DO NOT EDIT # Database Configuration (Unix Socket) DATABASE_URL=postgresql://{{ service_db_user }}@/{{ service_db_name }}?host={{ postgresql_unix_socket_directories }} DB_PASSWORD={{ service_db_password }} # Cache Configuration (Unix Socket) CACHE_URL=unix://{{ valkey_unix_socket_path }}?db={{ service_valkey_db }}&password={{ valkey_password }} # Application Configuration SECRET_KEY={{ service_secret_key }} LOG_LEVEL={{ service_log_level }} BIND_ADDRESS={{ service_bind_address }}:{{ service_port }} ``` ## Troubleshooting ### Socket Permission Issues If you get permission denied errors: 1. **Check group membership**: ```bash groups myservice # Should show: myservice postgres valkey ``` 2. **Verify container annotations**: ```bash podman inspect myservice --format='{{.Config.Annotations}}' # Should include: run.oci.keep_original_groups=1 ``` 3. **Check socket permissions**: ```bash ls -la /var/run/postgresql/ ls -la /var/run/valkey/ ``` ### Connection Issues 1. **Test socket access from host**: ```bash sudo -u myservice psql -h /var/run/postgresql -U myservice myservice_db sudo -u myservice redis-cli -s /var/run/valkey/valkey.sock ping ``` 2. **Check URL format**: - Use single `CACHE_URL` instead of separate variables - Include password in URL if required - Verify database number is correct ### Container Issues 1. **Check container user**: ```bash podman exec myservice id # Should show correct UID and supplementary groups ``` 2. **Verify socket mounts**: ```bash podman exec myservice ls -la /var/run/postgresql/ podman exec myservice ls -la /var/run/valkey/ ``` ## Best Practices 1. **Security**: - Use dedicated system users for each service - Limit group memberships to required infrastructure - Use vault variables for secrets 2. **Configuration**: - Use single URL format for Redis connections - Mount socket directories with appropriate SELinux labels - Include `run.oci.keep_original_groups=1` annotation 3. **Deployment**: - Test socket access before container deployment - Use proper dependency ordering in playbooks - Include database and cache setup tasks 4. **Monitoring**: - Monitor socket file permissions - Check service logs for connection errors - Verify group memberships after user changes ## Example Integration See the `authentik` role for a complete example of this pattern: - **Templates**: `roles/authentik/templates/` - **Tasks**: `roles/authentik/tasks/` - **Documentation**: `roles/authentik/README.md` This provides a working reference implementation for Unix socket integration.