9.8 KiB
Docker Deployment with Infisical Secrets
Learning objective: See best practices in action - secrets management, error handling, and idempotency.
What This Example Demonstrates
This playbook showcases production-ready Ansible patterns from Virgo-Core:
✅ Secrets Management:
- Infisical integration using reusable task
- Fallback to environment variables
no_log: trueon sensitive tasks
✅ Error Handling:
- Pre-flight checks with
assert changed_whenfor idempotencyfailed_whenfor graceful failures- Block/rescue for rollback
✅ Best Practices:
- Fully qualified module names (FQCN)
- Task organization with blocks
- Handlers for service restarts
- Verification steps
✅ Docker Operations:
- Idempotent container management
- Health checks with retries
- Proper logging on failures
Prerequisites
1. Infisical Setup
Universal Auth credentials:
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="ua-abc123"
export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="secret-xyz789"
OR fallback environment variables:
export DB_PASSWORD="fallback-db-password"
export API_KEY="fallback-api-key"
export REDIS_PASSWORD="fallback-redis-password"
2. Ansible Collections
# Install required collections
cd ../../.. # Back to ansible directory
uv run ansible-galaxy collection install -r requirements.yml
3. Target Hosts
Update inventory with Docker hosts:
# inventory/hosts
[docker_hosts]
docker-01-nexus.spaceships.work
4. Templates (create these)
The playbook references templates you need to create:
templates/app-config.yml.j2:
database:
host: db.spaceships.work
password: "{{ db_password }}"
api:
key: "{{ api_key }}"
redis:
host: redis.spaceships.work
password: "{{ redis_password }}"
templates/docker-compose.yml.j2:
version: '3.8'
services:
app:
image: your-app:latest
environment:
- CONFIG_FILE=/config/config.yml
volumes:
- {{ app_dir }}/config.yml:/config/config.yml:ro
ports:
- "8080:8080"
Quick Start
1. Validate Playbook
Syntax check:
ansible-playbook docker-deployment.yml --syntax-check
Lint check:
ansible-lint docker-deployment.yml
Dry run:
ansible-playbook docker-deployment.yml --check
2. Run Playbook
# Full deployment
ansible-playbook -i ../../inventory/hosts docker-deployment.yml
# Specific tags
ansible-playbook -i ../../inventory/hosts docker-deployment.yml --tags secrets
ansible-playbook -i ../../inventory/hosts docker-deployment.yml --tags deploy
ansible-playbook -i ../../inventory/hosts docker-deployment.yml --tags verify
3. Verify Deployment
# Check application health
curl http://docker-01-nexus.spaceships.work:8080/health
# Check Docker containers
ssh ansible@docker-01-nexus.spaceships.work "docker ps"
Understanding the Patterns
Pattern 1: Infisical Secret Lookup
The Pattern:
- name: Retrieve database password from Infisical
ansible.builtin.include_tasks: ../../tasks/infisical-secret-lookup.yml
vars:
secret_name: 'DB_PASSWORD'
secret_var_name: 'db_password'
fallback_env_var: 'DB_PASSWORD'
Why it works:
- Reusable task (DRY principle)
- Validates authentication before retrieving
- Fallback to environment for local dev
- No secrets in logs
- Clear error messages
Learn more: ../../patterns/secrets-management.md
Pattern 2: Pre-flight Validation
The Pattern:
pre_tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- app_name is defined
fail_msg: "Required variables not set"
- name: Check if Docker is installed
ansible.builtin.command: which docker
register: docker_check
changed_when: false # Check doesn't change state
failed_when: false # Don't fail yet
Why it works:
- Fails fast with clear messages
- Prevents partial deployments
- Uses
changed_when: falsefor checks - Uses
failed_when: falseto check result later
Pattern 3: Idempotent Docker Operations
The Pattern:
- name: Check if container is already running
ansible.builtin.command: docker ps --filter name={{ app_name }}
register: container_check
changed_when: false
- name: Start Docker containers
ansible.builtin.command: docker-compose up -d
register: compose_up
changed_when: "'Creating' in compose_up.stderr or 'Starting' in compose_up.stderr"
when: container_check.stdout != app_name
Why it works:
- Check first, then create
- Only reports "changed" if actually started something
- Conditional execution with
when: - True idempotency
Pattern 4: Block/Rescue Error Handling
The Pattern:
- name: Docker Management Block
block:
- name: Pull images
# ... tasks ...
rescue:
- name: Show container logs on failure
ansible.builtin.command: docker-compose logs --tail=50
register: container_logs
- name: Report failure
ansible.builtin.fail:
msg: "Deployment failed: {{ container_logs.stdout }}"
Why it works:
- Groups related tasks
- Automatic rollback on failure
- Provides debugging info
- Clean error reporting
Learn more: ../../patterns/error-handling.md
Pattern 5: Health Checks with Retries
The Pattern:
- name: Wait for application to be healthy
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
register: health_check
until: health_check.status == 200
retries: 30
delay: 10
Why it works:
- Automatic retries for transient failures
- Configurable timeout (30 × 10s = 5 minutes)
- Fails clearly if never becomes healthy
Common Mistakes Avoided
This playbook avoids common anti-patterns:
❌ Anti-pattern 1: Hard-coded Secrets
# DON'T DO THIS!
- name: Deploy config
ansible.builtin.template:
src: config.j2
dest: /etc/app/config.yml
vars:
db_password: "MyPassword123" # NEVER!
✅ This playbook: Uses Infisical with fallback to environment
❌ Anti-pattern 2: Missing changed_when
# DON'T DO THIS!
- name: Start container
ansible.builtin.command: docker start myapp
# Always reports "changed" even if already running
✅ This playbook: Checks first, uses changed_when to detect actual changes
❌ Anti-pattern 3: No Error Handling
# DON'T DO THIS!
- name: Deploy app
ansible.builtin.command: deploy.sh
# No check if it worked, no cleanup on failure
✅ This playbook: Uses block/rescue, verifies success
❌ Anti-pattern 4: Secrets in Logs
# DON'T DO THIS!
- name: Set password
ansible.builtin.command: set-password {{ password }}
# Password visible in Ansible output!
✅ This playbook: Uses no_log: true on sensitive tasks
Customization
Different Application
Change variables:
vars:
app_name: "my-other-app"
app_dir: "/opt/my-other-app"
Different Secrets
Add more secret retrievals:
- name: Retrieve JWT secret
ansible.builtin.include_tasks: ../../tasks/infisical-secret-lookup.yml
vars:
secret_name: 'JWT_SECRET'
secret_var_name: 'jwt_secret'
Skip Health Check
ansible-playbook docker-deployment.yml --skip-tags verify
Troubleshooting
Infisical Authentication Failed
Error: Missing Infisical authentication credentials
Solution:
# Check environment variables
echo $INFISICAL_UNIVERSAL_AUTH_CLIENT_ID
echo $INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET
# OR use fallback
export DB_PASSWORD="fallback-password"
Docker Not Installed
Error: Docker is not installed
Solution:
# Install Docker on target host
ssh ansible@docker-host
sudo apt update
sudo apt install docker.io docker-compose
Container Won't Start
Error: Docker deployment failed
Solution: Playbook shows logs automatically in rescue block. Review output for errors.
Manual check:
ssh ansible@docker-host
cd /opt/my-application
docker-compose logs
Health Check Timeout
Error: Wait for application to be healthy times out
Solution:
# Increase retries/delay
retries: 60 # 10 minutes
delay: 10
Testing the Playbook
Check Idempotency
# Run twice - second run should show no changes
ansible-playbook docker-deployment.yml
ansible-playbook docker-deployment.yml # Should be all "ok", no "changed"
Run Linters
# Ansible lint
ansible-lint docker-deployment.yml
# Custom idempotency check
../../tools/check_idempotency.py docker-deployment.yml
# Full lint suite
../../tools/lint-all.sh
Next Steps
Learn More Patterns
- Error Handling: ../../patterns/error-handling.md
- Secrets Management: ../../patterns/secrets-management.md
- Common Mistakes: ../../anti-patterns/common-mistakes.md
Additional Examples
- Basic Playbook:
../01-basic-playbook/- Simpler starting point - Repository Playbooks:
../../../ansible/playbooks/- Real production playbooks
Best Practices
Review the main skill:
- ../../SKILL.md - Complete best practices guide
Why These Patterns Matter
In Production:
- ✅ Secrets never in version control
- ✅ Playbooks are truly idempotent
- ✅ Clear error messages for troubleshooting
- ✅ Audit trail for all operations
- ✅ Rollback on failures
For Teams:
- ✅ Consistent patterns across playbooks
- ✅ Easy to understand and maintain
- ✅ Self-documenting code
- ✅ Reduced bus factor
For You:
- ✅ Confidence in deployments
- ✅ Less time debugging
- ✅ Better sleep at night!