This guide provides a production-ready Ansible playbook for deploying Infisical with Docker Compose on Debian, Ubuntu, and RHEL-compatible systems. The playbook automates the entire deployment process including Docker installation, security hardening, TLS configuration, and backup setup.
Current Version: v0.158.5 (February 2026)
Using Ansible for Infisical deployment provides:
| Requirement | Specification |
|---|---|
| OS | Debian 11+, Ubuntu 20.04+, RHEL 9+, Rocky Linux 9+, AlmaLinux 9+ |
| CPU | 4+ cores (recommended) |
| RAM | 4+ GB (recommended) |
| Disk | 50+ GB SSD |
| Network | Public IP with DNS configured |
infisical-ansible/
├── inventory/
│ ├── hosts.yml
│ └── group_vars/
│ └── infisical.yml
├── playbooks/
│ ├── deploy.yml
│ ├── backup.yml
│ └── update.yml
├── roles/
│ └── infisical/
│ ├── tasks/
│ ├── templates/
│ ├── files/
│ └── handlers/
├── ansible.cfg
└── README.md
mkdir -p infisical-ansible/{inventory/group_vars,playbooks,roles/infisical/{tasks,templates,files,handlers}}
cd infisical-ansible
cat > inventory/hosts.yml << 'EOF'
---
all:
children:
infisical:
hosts:
infisical-prod:
ansible_host: 192.168.1.100
ansible_user: deploy
site_url: infisical.example.com
ssl_email: admin@example.com
vars:
ansible_python_interpreter: /usr/bin/python3
EOF
cat > inventory/group_vars/infisical.yml << 'EOF'
---
# ===========================================
# Infisical Deployment Variables
# ===========================================
# Application Settings
infisical_version: "v0.93.1"
infisical_app_root: /opt/infisical
infisical_data_dir: /var/lib/infisical
infisical_backup_dir: /var/backups/infisical
infisical_log_dir: /var/log/infisical
# Network Settings
infisical_host_port: "127.0.0.1:8080"
infisical_container_port: 8080
# SSL/TLS Settings
infisical_ssl_enabled: true
infisical_ssl_provider: letsencrypt # letsencrypt, selfsigned, custom
# Resource Limits
infisical_cpu_limit: "2.0"
infisical_memory_limit: "2G"
infisical_cpu_request: "0.5"
infisical_memory_request: "512M"
# Database Settings (embedded PostgreSQL)
infisical_db_name: infisical
infisical_db_user: infisical
infisical_db_data_dir: "{{ infisical_data_dir }}/postgres"
# Redis Settings
infisical_redis_data_dir: "{{ infisical_data_dir }}/redis"
# Backup Settings
infisical_backup_enabled: true
infisical_backup_retention_days: 7
infisical_backup_schedule: "0 2 * * *" # Daily at 2 AM
# Security Settings
infisical_firewall_enabled: true
infisical_ssh_port: 22
infisical_allowed_ports:
- 80 # HTTP (for Let's Encrypt)
- 443 # HTTPS
- "{{ infisical_ssh_port }}" # SSH
# Monitoring Settings
infisical_healthcheck_enabled: true
infisical_telemetry_enabled: false
EOF
cat > playbooks/deploy.yml << 'EOF'
---
- name: Deploy Infisical Secret Management Platform
hosts: infisical
become: true
gather_facts: true
vars:
infisical_encryption_key: "{{ lookup('password', '/dev/null chars=hexdigits length=32') }}"
infisical_auth_secret: "{{ lookup('password', '/dev/null chars=base64 length=32') }}"
infisical_db_password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=24') }}"
pre_tasks:
- name: Validate target OS
ansible.builtin.assert:
that:
- ansible_os_family in ['Debian', 'RedHat']
- ansible_distribution_major_version | int >= 10 if ansible_os_family == 'Debian' else ansible_distribution_major_version | int >= 9
fail_msg: "Unsupported OS: {{ ansible_distribution }} {{ ansible_distribution_major_version }}"
success_msg: "Supported OS detected: {{ ansible_distribution }} {{ ansible_distribution_major_version }}"
- name: Check minimum RAM
ansible.builtin.assert:
that:
- ansible_memtotal_mb >= 2048
fail_msg: "Insufficient memory: {{ ansible_memtotal_mb }}MB (minimum 2048MB)"
roles:
- role: infisical
tags: ['infisical', 'deploy']
post_tasks:
- name: Display deployment summary
ansible.builtin.debug:
msg: |
===========================================
Infisical Deployment Complete!
===========================================
URL: https://{{ site_url }}
Data Directory: {{ infisical_data_dir }}
Backup Directory: {{ infisical_backup_dir }}
IMPORTANT: Save these credentials securely!
Encryption Key: {{ infisical_encryption_key }}
Auth Secret: {{ infisical_auth_secret }}
DB Password: {{ infisical_db_password }}
===========================================
run_once: true
delegate_to: localhost
EOF
cat > roles/infisical/tasks/main.yml << 'EOF'
---
# ===========================================
# Infisical Ansible Role - Main Tasks
# ===========================================
- name: Include OS-specific variables
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- files:
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version }}.yml"
- "{{ ansible_distribution | lower }}.yml"
- "{{ ansible_os_family | lower }}.yml"
- default.yml
paths:
- vars
tags: ['always']
- name: Install Docker
ansible.builtin.include_tasks: docker.yml
tags: ['docker', 'prerequisites']
- name: Create directory structure
ansible.builtin.include_tasks: directories.yml
tags: ['directories']
- name: Generate and store secrets
ansible.builtin.include_tasks: secrets.yml
tags: ['secrets']
- name: Deploy Docker Compose configuration
ansible.builtin.include_tasks: compose.yml
tags: ['compose']
- name: Configure Nginx reverse proxy
ansible.builtin.include_tasks: nginx.yml
tags: ['nginx']
when: infisical_ssl_enabled
- name: Configure firewall
ansible.builtin.include_tasks: firewall.yml
tags: ['firewall']
when: infisical_firewall_enabled
- name: Setup backup automation
ansible.builtin.include_tasks: backup.yml
tags: ['backup']
when: infisical_backup_enabled
- name: Start Infisical services
ansible.builtin.include_tasks: start.yml
tags: ['start']
- name: Health check
ansible.builtin.include_tasks: healthcheck.yml
tags: ['healthcheck']
EOF
cat > roles/infisical/tasks/docker.yml << 'EOF'
---
- name: Install Docker on Debian/Ubuntu
when: ansible_os_family == "Debian"
block:
- name: Install prerequisites
ansible.builtin.apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: true
- name: Add Docker GPG key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
- name: Install Docker on RHEL family
when: ansible_os_family == "RedHat"
block:
- name: Install prerequisites
ansible.builtin.dnf:
name:
- yum-utils
- device-mapper-persistent-data
- lvm2
state: present
- name: Add Docker repository
ansible.builtin.command:
cmd: yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
creates: /etc/yum.repos.d/docker-ce.repo
- name: Install Docker packages
ansible.builtin.dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
- name: Enable and start Docker service
ansible.builtin.systemd:
name: docker
state: started
enabled: true
- name: Add user to docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true
EOF
cat > roles/infisical/tasks/directories.yml << 'EOF'
---
- name: Create application directory
ansible.builtin.file:
path: "{{ infisical_app_root }}"
state: directory
mode: '0755'
owner: root
group: root
- name: Create data directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0750'
owner: root
group: root
loop:
- "{{ infisical_data_dir }}"
- "{{ infisical_data_dir }}/postgres"
- "{{ infisical_data_dir }}/redis"
- "{{ infisical_data_dir }}/nginx"
- "{{ infisical_data_dir }}/nginx/certs"
- "{{ infisical_backup_dir }}"
- "{{ infisical_log_dir }}"
- name: Create systemd directory
ansible.builtin.file:
path: /etc/systemd/system
state: directory
mode: '0755'
EOF
cat > roles/infisical/tasks/secrets.yml << 'EOF'
---
- name: Check if secrets file exists
ansible.builtin.stat:
path: "{{ infisical_app_root }}/.env"
register: env_file_stat
- name: Generate secrets file
ansible.builtin.template:
src: env.j2
dest: "{{ infisical_app_root }}/.env"
mode: '0600'
owner: root
group: root
when: not env_file_stat.stat.exists
no_log: true
- name: Store secrets securely
ansible.builtin.copy:
content: |
# Infisical Secrets - Store Securely!
# Generated: {{ ansible_date_time.iso8601 }}
ENCRYPTION_KEY={{ infisical_encryption_key }}
AUTH_SECRET={{ infisical_auth_secret }}
DB_PASSWORD={{ infisical_db_password }}
dest: "{{ infisical_backup_dir }}/infisical_secrets_{{ ansible_date_time.date }}.txt"
mode: '0600'
owner: root
group: root
when: not env_file_stat.stat.exists
no_log: true
EOF
cat > roles/infisical/tasks/compose.yml << 'EOF'
---
- name: Deploy Docker Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ infisical_app_root }}/docker-compose.yml"
mode: '0644'
owner: root
group: root
- name: Deploy Nginx configuration template
ansible.builtin.template:
src: nginx.conf.j2
dest: "{{ infisical_data_dir }}/nginx/nginx.conf"
mode: '0644'
owner: root
group: root
when: infisical_ssl_enabled
EOF
cat > roles/infisical/tasks/nginx.yml << 'EOF'
---
- name: Install Nginx
when: ansible_os_family == "Debian"
ansible.builtin.apt:
name: nginx
state: present
- name: Install Nginx (RHEL)
when: ansible_os_family == "RedHat"
ansible.builtin.dnf:
name: nginx
state: present
- name: Deploy Nginx configuration
ansible.builtin.template:
src: nginx-site.conf.j2
dest: /etc/nginx/sites-available/infisical
mode: '0644'
notify: reload nginx
- name: Enable Nginx site
ansible.builtin.file:
src: /etc/nginx/sites-available/infisical
dest: /etc/nginx/sites-enabled/infisical
state: link
notify: reload nginx
- name: Enable and start Nginx
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
- name: Obtain SSL certificate with Let's Encrypt
when: infisical_ssl_provider == "letsencrypt"
block:
- name: Install Certbot
ansible.builtin.package:
name: certbot
state: present
- name: Stop Nginx temporarily for certificate issuance
ansible.builtin.systemd:
name: nginx
state: stopped
- name: Obtain certificate
ansible.builtin.command:
cmd: >
certbot certonly --standalone
-d {{ site_url }}
--email {{ ssl_email }}
--agree-tos
--non-interactive
creates: /etc/letsencrypt/live/{{ site_url }}/fullchain.pem
- name: Copy certificates to Infisical directory
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: '0644'
remote_src: true
loop:
- src: /etc/letsencrypt/live/{{ site_url }}/fullchain.pem
dest: "{{ infisical_data_dir }}/nginx/certs/cert.pem"
- src: /etc/letsencrypt/live/{{ site_url }}/privkey.pem
dest: "{{ infisical_data_dir }}/nginx/certs/key.pem"
- name: Start Nginx
ansible.builtin.systemd:
name: nginx
state: started
EOF
cat > roles/infisical/tasks/firewall.yml << 'EOF'
---
- name: Install UFW (Debian/Ubuntu)
when: ansible_os_family == "Debian"
ansible.builtin.apt:
name: ufw
state: present
- name: Configure UFW defaults
when: ansible_os_family == "Debian"
ansible.builtin.lineinfile:
path: /etc/default/ufw
regexp: "^DEFAULT_INPUT_POLICY"
line: 'DEFAULT_INPUT_POLICY="DROP"'
- name: Allow required ports through UFW
when: ansible_os_family == "Debian"
community.general.ufw:
rule: allow
port: "{{ item }}"
loop: "{{ infisical_allowed_ports }}"
- name: Enable UFW
when: ansible_os_family == "Debian"
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Install firewalld (RHEL)
when: ansible_os_family == "RedHat"
ansible.builtin.dnf:
name: firewalld
state: present
- name: Enable and start firewalld
when: ansible_os_family == "RedHat"
ansible.builtin.systemd:
name: firewalld
state: started
enabled: true
- name: Configure firewalld ports
when: ansible_os_family == "RedHat"
ansible.posix.firewalld:
port: "{{ item }}/tcp"
permanent: true
state: enabled
loop: "{{ infisical_allowed_ports }}"
notify: reload firewalld
EOF
cat > roles/infisical/tasks/backup.yml << 'EOF'
---
- name: Deploy backup script
ansible.builtin.template:
src: backup.sh.j2
dest: "{{ infisical_app_root }}/backup.sh"
mode: '0755'
owner: root
group: root
- name: Setup backup cron job
ansible.builtin.cron:
name: "Infisical backup"
minute: "0"
hour: "2"
job: "{{ infisical_app_root }}/backup.sh >> {{ infisical_log_dir }}/backup.log 2>&1"
user: root
EOF
cat > roles/infisical/tasks/start.yml << 'EOF'
---
- name: Run database migrations
ansible.builtin.command:
cmd: docker compose run --rm db-migration
chdir: "{{ infisical_app_root }}"
register: migration_result
changed_when: "'already up to date' not in migration_result.stdout"
- name: Start Infisical services
ansible.builtin.command:
cmd: docker compose up -d
chdir: "{{ infisical_app_root }}"
register: compose_result
- name: Wait for Infisical to be ready
ansible.builtin.wait_for:
port: "{{ infisical_container_port }}"
host: "{{ '127.0.0.1' if '127.0.0.1' in infisical_host_port else '0.0.0.0' }}"
timeout: 120
delay: 10
EOF
cat > roles/infisical/tasks/healthcheck.yml << 'EOF'
---
- name: Check container status
ansible.builtin.command:
cmd: docker compose ps
chdir: "{{ infisical_app_root }}"
register: container_status
changed_when: false
- name: Display container status
ansible.builtin.debug:
var: container_status.stdout_lines
- name: Verify Infisical API health
ansible.builtin.uri:
url: "http://127.0.0.1:{{ infisical_container_port }}/api/status"
method: GET
status_code: 200
register: api_health
until: api_health.status == 200
retries: 10
delay: 10
- name: Display health check result
ansible.builtin.debug:
msg: "Infisical API is healthy and responding!"
when: api_health.status == 200
EOF
cat > roles/infisical/templates/env.j2 << 'EOF'
# ===========================================
# Infisical Environment Configuration
# Generated by Ansible - {{ ansible_date_time.iso8601 }}
# ===========================================
# Site Configuration
SITE_URL=https://{{ site_url }}
# Security Keys
ENCRYPTION_KEY={{ infisical_encryption_key }}
AUTH_SECRET={{ infisical_auth_secret }}
# PostgreSQL Configuration
POSTGRES_USER={{ infisical_db_user }}
POSTGRES_PASSWORD={{ infisical_db_password }}
POSTGRES_DB={{ infisical_db_name }}
DB_CONNECTION_URI=postgres://{{ infisical_db_user }}:{{ infisical_db_password }}@db:5432/{{ infisical_db_name }}
# Redis Configuration
REDIS_URL=redis://redis:6379
# JWT Token Lifetimes
JWT_AUTH_LIFETIME=15m
JWT_REFRESH_LIFETIME=24h
JWT_SERVICE_LIFETIME=1h
# Telemetry
TELEMETRY_ENABLED={{ infisical_telemetry_enabled | default('false') | lower }}
EOF
cat > roles/infisical/templates/docker-compose.yml.j2 << 'EOF'
services:
backend:
image: infisical/infisical:{{ infisical_version }}-postgres
container_name: infisical-backend
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db-migration:
condition: service_completed_successfully
env_file: .env
environment:
- NODE_ENV=production
ports:
- "{{ infisical_host_port }}:{{ infisical_container_port }}"
networks:
- infisical
volumes:
- {{ infisical_backup_dir }}:/app/backups
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:{{ infisical_container_port }}/api/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
read_only: true
tmpfs:
- /tmp:rw,exec,size=1G
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
cpus: '{{ infisical_cpu_limit }}'
memory: {{ infisical_memory_limit }}
reservations:
cpus: '{{ infisical_cpu_request }}'
memory: {{ infisical_memory_request }}
redis:
image: redis:7-alpine
container_name: infisical-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- {{ infisical_redis_data_dir }}:/data
networks:
- infisical
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
read_only: true
cap_drop:
- ALL
db:
image: postgres:14-alpine
container_name: infisical-db
restart: unless-stopped
env_file: .env
volumes:
- {{ infisical_db_data_dir }}:/var/lib/postgresql/data
networks:
- infisical
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ infisical_db_user }} -d {{ infisical_db_name }}"]
interval: 10s
timeout: 5s
retries: 10
db-migration:
image: infisical/infisical:{{ infisical_version }}-postgres
container_name: infisical-db-migration
depends_on:
db:
condition: service_healthy
env_file: .env
command: npm run migration:latest
networks:
- infisical
read_only: true
cap_drop:
- ALL
networks:
infisical:
driver: bridge
EOF
cat > roles/infisical/templates/backup.sh.j2 << 'EOF'
#!/bin/bash
set -e
BACKUP_DIR="{{ infisical_backup_dir }}"
DATE=$(date +%Y%m%d_%H%M%S)
DB_CONTAINER="infisical-db"
DB_USER="{{ infisical_db_user }}"
DB_NAME="{{ infisical_db_name }}"
RETENTION_DAYS={{ infisical_backup_retention_days }}
echo "Starting Infisical backup: $DATE"
mkdir -p $BACKUP_DIR/postgres
# Backup PostgreSQL
docker exec $DB_CONTAINER pg_dump -U $DB_USER $DB_NAME | \
gzip > $BACKUP_DIR/postgres/infisical_db_$DATE.sql.gz
# Backup environment file
cp {{ infisical_app_root }}/.env $BACKUP_DIR/infisical_env_$DATE.backup
# Cleanup old backups
find $BACKUP_DIR/postgres -name "*.gz" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.backup" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $DATE"
EOF
cat > roles/infisical/handlers/main.yml << 'EOF'
---
- name: reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
- name: reload firewalld
ansible.builtin.systemd:
name: firewalld
state: reloaded
- name: restart infisical
ansible.builtin.command:
cmd: docker compose restart
chdir: "{{ infisical_app_root }}"
EOF
cat > ansible.cfg << 'EOF'
[defaults]
inventory = inventory/hosts.yml
roles_path = roles
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callbacks_enabled = profile_tasks
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-%%h-%%p-%%r
EOF
# Test playbook (dry run)
ansible-playbook playbooks/deploy.yml --check
# Run deployment
ansible-playbook playbooks/deploy.yml
# Deploy with specific tags
ansible-playbook playbooks/deploy.yml --tags 'docker,compose'
# Deploy to specific host
ansible-playbook playbooks/deploy.yml --limit infisical-prod
EOF
# Connect to server and check status
ssh deploy@192.168.1.100
cd /opt/infisical
docker compose ps
docker compose logs -f backend
cat > playbooks/update.yml << 'EOF'
---
- name: Update Infisical
hosts: infisical
become: true
vars:
infisical_version: "v0.93.1" # Update this version
tasks:
- name: Pull latest image
ansible.builtin.command:
cmd: docker compose pull
chdir: "{{ infisical_app_root }}"
- name: Stop services
ansible.builtin.command:
cmd: docker compose down
chdir: "{{ infisical_app_root }}"
- name: Run migrations
ansible.builtin.command:
cmd: docker compose run --rm db-migration
chdir: "{{ infisical_app_root }}"
- name: Start services
ansible.builtin.command:
cmd: docker compose up -d
chdir: "{{ infisical_app_root }}"
- name: Verify health
ansible.builtin.uri:
url: "http://127.0.0.1:8080/api/status"
method: GET
register: health
until: health.status == 200
retries: 10
delay: 10
EOF
cat > playbooks/backup.yml << 'EOF'
---
- name: Backup Infisical
hosts: infisical
become: true
tasks:
- name: Run backup script
ansible.builtin.command:
cmd: "{{ infisical_app_root }}/backup.sh"
register: backup_result
- name: Display backup result
ansible.builtin.debug:
var: backup_result.stdout_lines
EOF
cat > roles/infisical/vars/debian.yml << 'EOF'
---
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
nginx_package: nginx
firewall_package: ufw
firewall_service: ufw
EOF
cat > roles/infisical/vars/redhat.yml << 'EOF'
---
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
nginx_package: nginx
firewall_package: firewalld
firewall_service: firewalld
EOF
Any questions?
Feel free to contact us. Find all contact information on our contact page.