Files
rick-infra/docs/service-integration-guide.md
Joakim b42ee2a22b Fix: Complete authentik Quadlet implementation with networking solution
Resolves authentik deployment issues by implementing proper Podman Quadlet
configuration and fixing networking for external access through Caddy.

Core Fixes:
• Add missing [Install] sections to container Quadlet files for systemd service generation
• Fix pod references from 'systemd-authentik' to 'authentik.pod' for proper Quadlet linking
• Remove problematic --userns=host to use proper rootless user namespaces
• Configure subuid/subgid ranges for authentik user (200000:65536)
• Update networking to bind 0.0.0.0:9000 only (remove unnecessary HTTPS port 9443)
• Add AUTHENTIK_LISTEN__HTTP=0.0.0.0:9000 environment configuration
• Fix Caddy reverse proxy to use HTTP backend instead of HTTPS

Infrastructure Updates:
• Enhance PostgreSQL role with Unix socket configuration and user management
• Improve Valkey role with proper systemd integration and socket permissions
• Add comprehensive service integration documentation
• Update deployment playbooks with backup and restore capabilities

Security Improvements:
• Secure network isolation with Caddy SSL termination
• Reduced attack surface by removing direct HTTPS container exposure
• Proper rootless container configuration with user namespace mapping

Result: authentik now fully operational with external HTTPS access via auth.jnss.me
All systemd services (authentik-pod, authentik-server, authentik-worker) running correctly.
2025-12-04 19:42:31 +01:00

8.6 KiB

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:

- 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)

[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)

[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:

# 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):

# 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:

# 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:

- 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

# 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:

    groups myservice
    # Should show: myservice postgres valkey
    
  2. Verify container annotations:

    podman inspect myservice --format='{{.Config.Annotations}}'
    # Should include: run.oci.keep_original_groups=1
    
  3. Check socket permissions:

    ls -la /var/run/postgresql/
    ls -la /var/run/valkey/
    

Connection Issues

  1. Test socket access from host:

    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:

    podman exec myservice id
    # Should show correct UID and supplementary groups
    
  2. Verify socket mounts:

    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.