====== IAM Production Deployment Guide ====== ===== Keycloak + PostgreSQL + SOPS (AlmaLinux 9) ===== ==== Target Stack ==== * OS: AlmaLinux 9 * Containers: Docker Engine + Compose * IAM: Keycloak * DB: PostgreSQL 16 * Secret Encryption: Mozilla SOPS * Key Backend: age * SELinux: Enforcing mode ==== Deployment Strategy ==== {{:iambootprocessandbackup.jpg?nolink&|iambootprocessandbackup.jpg}} ===== 1. Install Dependencies ===== Update System # This command requires server reboot. sudo dnf update -y sudo dnf install -y epel-release ==== Install Docker ==== sudo dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin Enable: sudo systemctl enable docker sudo systemctl start docker Verify: docker --version docker compose version ==== Install SOPS & AGE ==== sudo dnf install -y sops age Verify: sops --version age --version ===== 2. Create Production Directory Structure ===== sudo mkdir -p /opt/cotrav/iam/{data/postgres,secrets,logs,keycloak-backups} sudo mkdir -p /etc/cotrav/sops/age Set permissions: sudo chown -R $USER:$USER /opt/cotrav sudo chown -R root:root /etc/cotrav/sops sudo chmod 700 /etc/cotrav/sops ===== 3. Generate AGE Key (One-Time Task) ===== sudo age-keygen -o /etc/cotrav/sops/age/keys.txt sudo chmod 600 /etc/cotrav/sops/age/keys.txt Extract public key: grep "public key:" /etc/cotrav/sops/age/keys.txt Save that public key value. ===== 4. Create & Encrypt Secrets ===== Navigate: cd /opt/cotrav/iam/secrets Create temporary plaintext: nano secrets.yaml Secrets: db_password: StrongDBPass@2026! admin_password: StrongAdminPass@2026! Set key: export AGE_PUBLIC_KEY=$(grep "public key:" /etc/cotrav/sops/age/keys.txt | awk '{print $4}') # Verify value echo $AGE_PUBLIC_KEY Encrypt: sops --encrypt \ --age "$AGE_PUBLIC_KEY" \ --encrypted-regex '^(db_password|admin_password)$' \ secrets.yaml> secrets.enc.yaml Secure delete plaintext: shred -u secrets.yaml Only ''secrets.enc.yaml'' remains. ===== 5. Docker Compose (Production Version) ===== ''/opt/cotrav/iam/docker-compose.yml'' services: postgres: image: postgres:16 container_name: keycloak_db environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak_user POSTGRES_PASSWORD_FILE: /run/secrets/db_password volumes: - /opt/cotrav/iam/data/postgres:/var/lib/postgresql/data:Z - /dev/shm/iam-secrets/db_password:/run/secrets/db_password:ro,Z healthcheck: test: ["CMD-SHELL", "pg_isready -U keycloak_user -d keycloak"] interval: 5s networks: - iam_network keycloak: image: quay.io/keycloak/keycloak:26.1.0 container_name: keycloak_app command: start ports: - "8080:8080" environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak_db:5432/keycloak KC_DB_USERNAME: keycloak_user KC_DB_PASSWORD_FILE: /run/secrets/db_password KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/admin_password KC_HTTP_ENABLED: "true" volumes: - /dev/shm/iam-secrets/db_password:/run/secrets/db_password:ro,Z - /dev/shm/iam-secrets/admin_password:/run/secrets/admin_password:ro,Z - /opt/cotrav/iam/logs:/opt/keycloak/data/log:Z depends_on: postgres: condition: service_healthy networks: - iam_network networks: iam_network: driver: bridge ===== 6. Create Secure start-iam.sh ===== ''/opt/cotrav/iam/start-iam.sh'' #!/bin/bash set -e echo "Setting SOPS key..." export SOPS_AGE_KEY_FILE=/etc/cotrav/sops/age/keys.txt echo "Creating RAM directory..." mkdir -p /dev/shm/iam-secrets chmod 700 /dev/shm/iam-secrets echo "Decrypting secrets to RAM..." sops --decrypt --extract '["db_password"]' \ /opt/cotrav/iam/secrets/secrets.enc.yaml \ > /dev/shm/iam-secrets/db_password sops --decrypt --extract '["admin_password"]' \ /opt/cotrav/iam/secrets/secrets.enc.yaml \ > /dev/shm/iam-secrets/admin_password chmod 600 /dev/shm/iam-secrets/* echo "Starting Docker..." cd /opt/cotrav/iam docker compose up -d echo "IAM stack started." Make executable: chmod +x /opt/cotrav/iam/start-iam.sh ===== 7. systemd Auto-Start Service ===== ''/etc/systemd/system/cotrav-iam.service'' [Unit] Description=Cotrav IAM Stack After=docker.service Requires=docker.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/opt/cotrav/iam/start-iam.sh ExecStop=/usr/bin/docker compose -f /opt/cotrav/iam/docker-compose.yml down [Install] WantedBy=multi-user.target Enable: sudo systemctl daemon-reload sudo systemctl enable cotrav-iam sudo systemctl start cotrav-iam ===== 8. SELinux Configuration (AlmaLinux 9) ===== sudo semanage fcontext -a -t container_file_t "/opt/travsetup(/.*)?"\\ sudo restorecon -Rv /opt/travsetup\\ Verify: ls -Z /opt/travsetup/iam Must show ''container_file_t''. ===== 9. Automated PostgreSQL Backup ===== /opt/travsetup/iam/backup-postgres.sh #!/bin/bash\ DATE=$(date +%F-%H%M) docker exec keycloak_db \ pg_dump -U keycloak_user keycloak \ > /opt/travsetup/iam/keycloak-backups/keycloak-$DATE.sql Make executable: chmod +x /opt/travsetup/iam/backup-postgres.sh\ Add cron job: crontab -e\ Add: 0 2 * * * /opt/travsetup/iam/backup-postgres.sh>> /opt/travsetup/iam/logs/backup.log 2>&1\ ===== Proxy through NGINX - Droplet FIX ===== To resolve on browser error "We are sorry… HTTPS required" **Step 1: Install Nginx on Alma Linux** bash dnf install -y nginx systemctl enable --now nginx **Step 2: Generate a self-signed certificate** bash mkdir -p /etc/nginx/ssl openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/keycloak.key \ -out /etc/nginx/ssl/keycloak.crt \ -subj "/CN=64.227.190.56" **Step 3: Create Nginx config for Keycloak** bash nano /etc/nginx/conf.d/keycloak.conf server { listen 443 ssl; server_name 64.227.190.56; ssl_certificate /etc/nginx/ssl/keycloak.crt; ssl_certificate_key /etc/nginx/ssl/keycloak.key; # Security headers add_header Strict-Transport-Security "max-age=31536000" always; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } } server { listen 80; server_name 64.227.190.56; return 301 https://$host$request_uri; } Modified docker-compose.yml as follows keycloak: image: quay.io/keycloak/keycloak:26.1.0 container_name: keycloak_app command: start ports: - "8080:8080" environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak_db:5432/keycloak KC_DB_USERNAME: keycloak_user KC_DB_PASSWORD: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: KC_HTTP_ENABLED: "true" KC_HTTP_PORT: "8080" KC_PROXY_HEADERS: xforwarded KC_HOSTNAME: "https://64.227.190.56" KC_HOSTNAME_STRICT: "false" Start NginX systemctl restart nginx Check/Configure Firewall rules for URL as follows **Configure Inbound Rules** Add these inbound rules: ^Type^Protocol^Port^Sources| |HTTP|TCP|80|All IPv4, All IPv6| |HTTPS|TCP|443|All IPv4, All IPv6| |SSH|TCP|22| \\ All IPv4, All IPv6| Allow Nginx to connect to local ports setsebool -P httpd_can_network_connect 1 #or setenforce 1 Stop docker and NginX and start again. ===== cPanel's userdata include ===== **Step 1: Create the userdata directories** bash mkdir -p /etc/apache2/conf.d/userdata/std/2_4/ctapi/kcloak.ctapi.in/ mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/ctapi/kcloak.ctapi.in/ **Step 2: Create HTTP proxy config** bash nano /etc/apache2/conf.d/userdata/std/2_4/ctapi/kcloak.ctapi.in/proxy.conf Add: RewriteEngine On RewriteRule ^(.*)$ https://kcloak.ctapi.in$1 [R=301,L] **Step 3: Create HTTPS proxy config** bash nano /etc/apache2/conf.d/userdata/ssl/2_4/ctapi/kcloak.ctapi.in/proxy.conf Add: ProxyPreserveHost On\ ProxyPass / http://127.0.0.1:8080/\ ProxyPassReverse / http://127.0.0.1:8080/\ RequestHeader set X-Forwarded-Proto "https"\ RequestHeader set X-Forwarded-Port "443" **Step 4: Rebuild Apache config and restart** bash /scripts/rebuildhttpdconf httpd -t systemctl restart httpd Then test: bash curl -I https://kcloak.ctapi.in Expected result: curl -I [[https://kcloak.ctapi.in/|https://kcloak.ctapi.in]] HTTP/1.1 302 Found Date: Thu, 26 Feb 2026 11:22:25 GMT Server: Apache Location: [[https://kcloak.ctapi.in/admin/|https://kcloak.ctapi.in/admin/]] Referrer-Policy: no-referrer Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Check for **Location: https://kcloak.ctapi.in/admin/|https://kcloak.ctapi.in/admin/** This is poining to correct directory and not apache direcoty with cgi folder.