# Deployment Strategies

Comprehensive guide to deploying Ansible playbooks to production, including AWX/Tower integration, deployment patterns, and production cutover.

## Deployment Overview

Successful deployment requires careful planning, testing, and execution. This guide covers strategies from simple direct deployment to enterprise-grade automation with AWX.

!!! warning "Production Safety"
    Always test in non-production environments first. Use check mode, limit flags, and rollback plans for every production deployment.

---

## Deployment Maturity Levels

```mermaid
graph LR
    A[Level 1:<br/>Manual] --> B[Level 2:<br/>Scripted]
    B --> C[Level 3:<br/>CI/CD]
    C --> D[Level 4:<br/>Self-Service]
    D --> E[Level 5:<br/>GitOps]

    style A fill:#ffebee
    style B fill:#fff3e0
    style C fill:#e3f2fd
    style D fill:#e8f5e9
    style E fill:#f3e5f5
```

| Level | Approach | Tools | Best For |
|-------|----------|-------|----------|
| 1 | Manual execution | `ansible-playbook` CLI | Development, learning |
| 2 | Shell scripts | Bash + Ansible | Small teams |
| 3 | CI/CD pipelines | Jenkins, GitLab CI | Medium orgs |
| 4 | Self-service platform | AWX, Tower | Enterprise |
| 5 | GitOps | ArgoCD, Flux | Cloud-native |

---

## Deployment Patterns

### Blue-Green Deployment

**Overview**: Maintain two identical environments, switch traffic after validation.

```mermaid
graph TD
    A[Current: Blue<br/>Production Traffic] --> B{Deploy to Green}
    B --> C[Test Green Environment]
    C --> D{Tests Pass?}
    D -->|Yes| E[Switch Traffic to Green]
    D -->|No| F[Fix Issues]
    F --> B
    E --> G[Green becomes Production]
    G --> H[Blue becomes Standby]

    style A fill:#2196f3,color:#fff
    style E fill:#4caf50,color:#fff
    style G fill:#4caf50,color:#fff
    style H fill:#2196f3,color:#fff
```

**Implementation:**

```yaml
---
- name: Blue-Green Deployment
  hosts: localhost
  gather_facts: false
  vars:
    current_env: blue
    target_env: green

  tasks:
    - name: Deploy to green environment
      ansible.builtin.include_role:
        name: application
      vars:
        environment: "{{ target_env }}"
        ansible_limit: "{{ target_env }}_servers"

    - name: Run smoke tests on green
      ansible.builtin.uri:
        url: "https://{{ target_env }}.example.com/health"
        status_code: 200
      register: health_check
      retries: 5
      delay: 10

    - name: Switch load balancer to green
      community.general.haproxy:
        backend: app_backend
        host: "{{ target_env }}_servers"
        state: enabled
      when: health_check.status == 200

    - name: Disable blue environment
      community.general.haproxy:
        backend: app_backend
        host: "{{ current_env }}_servers"
        state: disabled

    - name: Update environment marker
      ansible.builtin.set_fact:
        cacheable: true
        current_production: "{{ target_env }}"
```

**Benefits**:
- Zero downtime
- Instant rollback (switch back to blue)
- Full testing before cutover

**Drawbacks**:
- Requires double infrastructure
- Database migrations need special handling

### Canary Deployment

**Overview**: Gradually roll out to subset of servers, monitor, then expand.

```mermaid
graph LR
    A[Deploy to<br/>10% Canary] --> B{Monitor<br/>Metrics}
    B -->|Good| C[Deploy to<br/>50%]
    C --> D{Monitor<br/>Metrics}
    D -->|Good| E[Deploy to<br/>100%]
    B -->|Issues| F[Rollback]
    D -->|Issues| F

    style A fill:#fff3e0
    style C fill:#e3f2fd
    style E fill:#4caf50,color:#fff
    style F fill:#f44336,color:#fff
```

**Implementation:**

```yaml
---
- name: Canary Deployment
  hosts: app_servers
  serial: "{{ rollout_percentage | default('10%') }}"
  max_fail_percentage: 5

  tasks:
    - name: Deploy new version
      ansible.builtin.include_role:
        name: application
      vars:
        app_version: "{{ new_version }}"

    - name: Wait for service to stabilize
      ansible.builtin.pause:
        seconds: 30

    - name: Check service health
      ansible.builtin.uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: health
      retries: 3
      delay: 5

    - name: Monitor error rates
      ansible.builtin.command:
        cmd: curl -s http://localhost:9090/metrics | grep error_rate
      register: metrics
      failed_when: "'error_rate > 0.05' in metrics.stdout"

    - name: Pause for monitoring
      ansible.builtin.pause:
        prompt: "Review metrics before continuing (Enter to proceed)"
      when: rollout_percentage == '10%'
```

**Progressive Rollout:**

```bash
# Stage 1: 10% canary
ansible-playbook deploy.yml -e "rollout_percentage=10%" -e "new_version=2.0.0"

# Monitor for 15 minutes, check metrics

# Stage 2: 50% rollout
ansible-playbook deploy.yml -e "rollout_percentage=50%" -e "new_version=2.0.0"

# Monitor for 15 minutes

# Stage 3: Full rollout
ansible-playbook deploy.yml -e "rollout_percentage=100%" -e "new_version=2.0.0"
```

**Benefits**:
- Low risk (limited exposure)
- Real-world validation
- Easy to halt if issues arise

**Drawbacks**:
- Longer deployment time
- Requires good monitoring
- Version mixing during rollout

### Rolling Deployment

**Overview**: Update servers one at a time or in small batches.

```yaml
---
- name: Rolling Deployment
  hosts: app_servers
  serial: 2  # Update 2 servers at a time

  pre_tasks:
    - name: Remove server from load balancer
      community.general.haproxy:
        backend: app_backend
        host: "{{ inventory_hostname }}"
        state: disabled
      delegate_to: localhost

  tasks:
    - name: Stop application
      ansible.builtin.service:
        name: myapp
        state: stopped

    - name: Deploy new version
      ansible.builtin.include_role:
        name: application

    - name: Start application
      ansible.builtin.service:
        name: myapp
        state: started

    - name: Wait for service to be ready
      ansible.builtin.wait_for:
        port: 8080
        delay: 5
        timeout: 60

  post_tasks:
    - name: Add server back to load balancer
      community.general.haproxy:
        backend: app_backend
        host: "{{ inventory_hostname }}"
        state: enabled
      delegate_to: localhost

    - name: Health check
      ansible.builtin.uri:
        url: "http://{{ inventory_hostname }}:8080/health"
        status_code: 200
```

**Benefits**:
- No extra infrastructure needed
- Predictable deployment time
- Easy to automate

**Drawbacks**:
- Some downtime per server
- Requires load balancer
- Mixed versions during deployment

### Parallel Run Strategy

**Overview**: Run Chef and Ansible simultaneously during transition.

**Implementation:**

```yaml
---
- name: Parallel Run Verification
  hosts: migration_targets

  tasks:
    - name: Run Chef client
      ansible.builtin.command:
        cmd: chef-client
      register: chef_run
      changed_when: "'updated' in chef_run.stdout"

    - name: Capture Chef state
      ansible.builtin.command:
        cmd: knife node show {{ inventory_hostname }} -a
      register: chef_state
      delegate_to: localhost

    - name: Run Ansible playbook
      ansible.builtin.include_role:
        name: application
      register: ansible_run

    - name: Capture Ansible state
      ansible.builtin.setup:
      register: ansible_state

    - name: Compare states
      ansible.builtin.assert:
        that:
          - chef_state.rc == 0
          - ansible_run is succeeded
        msg: "Chef and Ansible runs must both succeed"

    - name: Report differences
      ansible.builtin.debug:
        msg: "Differences detected: {{ diff }}"
      vars:
        diff: "{{ chef_state.stdout | difference(ansible_state.ansible_facts) }}"
      when: diff | length > 0
```

**Benefits**:
- Low risk (Chef still active)
- Validates Ansible accuracy
- Gradual confidence building

**Drawbacks**:
- Requires Chef infrastructure
- May cause conflicts
- Extended transition period

---

## AWX/Ansible Tower Integration

### AWX Setup for SousChef

**Install AWX (via Docker Compose):**

=== "MCP (AI Assistant)"
    ```
    Generate AWX job templates for the converted playbooks in playbooks/ directory
    ```

=== "CLI"
    ```bash
    souschef-cli generate-awx-config \
      --playbooks-dir playbooks/ \
      --output awx_config.yml
    ```

**Generated AWX Configuration:**

```yaml
---
awx_projects:
  - name: Infrastructure Automation
    description: Converted Chef cookbooks
    organization: Engineering
    scm_type: git
    scm_url: https://github.com/myorg/ansible-playbooks.git
    scm_branch: main
    scm_update_on_launch: true

awx_inventories:
  - name: Production
    description: Production servers
    organization: Engineering
    variables:
      environment: production
      ansible_user: ansible

awx_job_templates:
  - name: Deploy Web Application
    job_type: run
    inventory: Production
    project: Infrastructure Automation
    playbook: playbooks/webserver.yml
    credentials:
      - SSH Key
      - Vault Password
    extra_vars:
      app_version: "{{ app_version }}"
    survey_enabled: true
    survey_spec:
      name: Deployment Survey
      description: Configure deployment parameters
      spec:
        - question_name: Application Version
          variable: app_version
          type: text
          required: true
        - question_name: Environment
          variable: target_env
          type: multiplechoice
          choices:
            - dev
            - staging
            - production
          required: true

  - name: Database Migration
    job_type: run
    inventory: Production
    project: Infrastructure Automation
    playbook: playbooks/database.yml
    credentials:
      - SSH Key
      - Database Credentials
    ask_variables_on_launch: true

awx_workflows:
  - name: Full Application Deployment
    organization: Engineering
    nodes:
      - identifier: pre_checks
        unified_job_template: Pre-Deployment Checks
        success_nodes:
          - database_migration

      - identifier: database_migration
        unified_job_template: Database Migration
        success_nodes:
          - app_deployment

      - identifier: app_deployment
        unified_job_template: Deploy Web Application
        success_nodes:
          - smoke_tests

      - identifier: smoke_tests
        unified_job_template: Smoke Tests
        success_nodes:
          - notify_success
        failure_nodes:
          - rollback

      - identifier: rollback
        unified_job_template: Rollback Deployment
        always_nodes:
          - notify_failure
```

### Import to AWX

**Using AWX CLI:**

```bash
# Install awxkit
pip install awxkit

# Login
export TOWER_HOST=https://awx.example.com
export TOWER_USERNAME=admin
export TOWER_PASSWORD=secret
export TOWER_VERIFY_SSL=false

# Import configuration
awx-cli receive --conf.host=$TOWER_HOST \
  --conf.username=$TOWER_USERNAME \
  --conf.password=$TOWER_PASSWORD \
  --conf.insecure < awx_config.yml
```

**Using AWX Collection:**

```yaml
---
- name: Configure AWX
  hosts: localhost
  gather_facts: false
  collections:
    - awx.awx

  tasks:
    - name: Create organization
      awx.awx.organization:
        name: Engineering
        description: Engineering team resources
        state: present

    - name: Create project
      awx.awx.project:
        name: Infrastructure Automation
        organization: Engineering
        scm_type: git
        scm_url: https://github.com/myorg/ansible-playbooks.git
        state: present

    - name: Create inventory
      awx.awx.inventory:
        name: Production
        organization: Engineering
        state: present

    - name: Create job template
      awx.awx.job_template:
        name: Deploy Web Application
        job_type: run
        organization: Engineering
        inventory: Production
        project: Infrastructure Automation
        playbook: playbooks/webserver.yml
        state: present
```

### Dynamic Inventory

**Chef Search Equivalent:**

**Chef:**
```ruby
# Search for web servers
search(:node, 'role:webserver AND chef_environment:production').each do |node|
  # Do something
end
```

**AWX Dynamic Inventory Script:**

```python
#!/usr/bin/env python3
"""
AWX dynamic inventory from Chef Server or custom CMDB.
"""
import json
import sys
import requests

def get_chef_nodes():
    """Query Chef Server for nodes."""
    response = requests.get(
        'https://chef-server.example.com/nodes',
        headers={'Authorization': f'Bearer {CHEF_TOKEN}'},
        verify=False
    )
    return response.json()

def build_inventory():
    """Build Ansible inventory from Chef data."""
    inventory = {
        '_meta': {
            'hostvars': {}
        },
        'all': {
            'hosts': [],
            'vars': {}
        }
    }

    nodes = get_chef_nodes()

    for node in nodes:
        hostname = node['name']
        inventory['all']['hosts'].append(hostname)

        # Add to role-based groups
        for role in node.get('roles', []):
            if role not in inventory:
                inventory[role] = {'hosts': []}
            inventory[role]['hosts'].append(hostname)

        # Add hostvars
        inventory['_meta']['hostvars'][hostname] = {
            'ansible_host': node['ipaddress'],
            'chef_environment': node['chef_environment'],
            'platform': node['platform'],
            'platform_version': node['platform_version']
        }

    return inventory

if __name__ == '__main__':
    if len(sys.argv) == 2 and sys.argv[1] == '--list':
        inventory = build_inventory()
        print(json.dumps(inventory, indent=2))
    else:
        print(json.dumps({}))
```

**Configure in AWX:**

1. Create inventory source
2. Set source to "Custom Script"
3. Upload inventory script
4. Set sync schedule (e.g., hourly)

---

## Secrets Management

### Ansible Vault Migration

**From Chef Encrypted Data Bags:**

```bash
# Extract Chef secrets
knife data bag show secrets database \
  --secret-file ~/.chef/encrypted_data_bag_secret \
  -Fj > database_secrets.json

# Convert to Ansible Vault format
cat > group_vars/production/vault.yml <<EOF
---
vault_db_host: $(jq -r '.host' database_secrets.json)
vault_db_port: $(jq -r '.port' database_secrets.json)
vault_db_user: $(jq -r '.username' database_secrets.json)
vault_db_password: $(jq -r '.password' database_secrets.json)
EOF

# Encrypt with Ansible Vault
ansible-vault encrypt group_vars/production/vault.yml
```

**Reference in Playbook:**

```yaml
---
- name: Configure application
  hosts: app_servers
  vars_files:
    - group_vars/production/vault.yml

  tasks:
    - name: Deploy database configuration
      ansible.builtin.template:
        src: database.yml.j2
        dest: /etc/app/database.yml
      vars:
        db_host: "{{ vault_db_host }}"
        db_password: "{{ vault_db_password }}"
      no_log: true
```

### AWX Credentials

**Create Credential Types:**

```yaml
---
- name: Create custom credential type for database
  awx.awx.credential_type:
    name: Database Credentials
    kind: cloud
    inputs:
      fields:
        - id: db_host
          type: string
          label: Database Host
        - id: db_port
          type: string
          label: Database Port
        - id: db_username
          type: string
          label: Username
        - id: db_password
          type: string
          label: Password
          secret: true
    injectors:
      extra_vars:
        database_host: "{{ db_host }}"
        database_port: "{{ db_port }}"
        database_username: "{{ db_username }}"
        database_password: "{{ db_password }}"
    state: present

- name: Create credential
  awx.awx.credential:
    name: Production Database
    organization: Engineering
    credential_type: Database Credentials
    inputs:
      db_host: postgres.example.com
      db_port: 5432
      db_username: app_user
      db_password: !vault |
        $ANSIBLE_VAULT;1.1;AES256
        ...encrypted...
    state: present
```

### External Secret Managers

**HashiCorp Vault Integration:**

```yaml
---
- name: Retrieve secrets from Vault
  hosts: app_servers

  tasks:
    - name: Get database credentials
      community.hashi_vault.vault_read:
        url: https://vault.example.com
        path: secret/data/production/database
        auth_method: token
        token: "{{ lookup('env', 'VAULT_TOKEN') }}"
      register: vault_secrets

    - name: Deploy configuration
      ansible.builtin.template:
        src: database.yml.j2
        dest: /etc/app/database.yml
      vars:
        db_password: "{{ vault_secrets.data.data.password }}"
      no_log: true
```

---

## Production Cutover

### Pre-Cutover Checklist

**Weeks Before:**

- [ ] Complete parallel runs (Chef + Ansible)
- [ ] Validate all conversions in staging
- [ ] Train operations team on Ansible
- [ ] Document rollback procedures
- [ ] Set up monitoring and alerting
- [ ] Schedule maintenance window
- [ ] Communicate to stakeholders

**Days Before:**

- [ ] Final parallel run validation
- [ ] Backup all Chef cookbooks
- [ ] Backup Chef Server configuration
- [ ] Review rollback plan with team
- [ ] Prepare runbooks
- [ ] Test rollback procedure
- [ ] Verify monitoring endpoints

**Day Of:**

- [ ] Announce maintenance window
- [ ] Take final Chef Server backup
- [ ] Freeze Chef cookbook changes
- [ ] Enable detailed logging
- [ ] Notify stakeholders (start)

### Cutover Execution

```mermaid
graph TD
    A[Start Maintenance] --> B[Final Chef Run]
    B --> C[Capture Final State]
    C --> D[Disable Chef Client]
    D --> E[Run Ansible Playbooks]
    E --> F[Validate Services]
    F --> G{All Services OK?}
    G -->|Yes| H[Enable Monitoring]
    G -->|No| I[Execute Rollback]
    I --> J[Re-enable Chef]
    J --> K[Investigate Issues]
    H --> L[End Maintenance]
    L --> M[Monitor for 24h]

    style A fill:#fff3e0
    style E fill:#e3f2fd
    style G fill:#f3e5f5
    style H fill:#4caf50,color:#fff
    style I fill:#f44336,color:#fff
    style M fill:#4caf50,color:#fff
```

**Cutover Playbook:**

```yaml
---
- name: Production Cutover - Chef to Ansible
  hosts: all
  gather_facts: true
  serial: 10%  # Process 10% of servers at a time

  pre_tasks:
    - name: Announce cutover start
      ansible.builtin.debug:
        msg: "Starting cutover for {{ inventory_hostname }} at {{ ansible_date_time.iso8601 }}"

    - name: Final Chef client run
      ansible.builtin.command:
        cmd: chef-client
      register: final_chef_run
      ignore_errors: true

    - name: Capture Chef-managed resources
      ansible.builtin.command:
        cmd: chef-client --why-run --format json
      register: chef_state

    - name: Disable Chef client
      ansible.builtin.service:
        name: chef-client
        state: stopped
        enabled: false

    - name: Remove Chef from cron
      ansible.builtin.cron:
        name: "Chef client"
        state: absent

  tasks:
    - name: Run Ansible configuration
      ansible.builtin.include_role:
        name: "{{ item }}"
      loop: "{{ required_roles }}"

    - name: Validate services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
      loop: "{{ critical_services }}"
      register: service_check

    - name: Run smoke tests
      ansible.builtin.uri:
        url: "http://localhost:{{ app_port }}/health"
        status_code: 200
      register: health_check
      retries: 3
      delay: 10

  post_tasks:
    - name: Mark cutover complete
      ansible.builtin.file:
        path: /etc/ansible_cutover_complete
        state: touch
        mode: '0644'

    - name: Log cutover completion
      ansible.builtin.lineinfile:
        path: /var/log/ansible-cutover.log
        line: "Cutover completed: {{ ansible_date_time.iso8601 }}"
        create: true

  rescue:
    - name: Cutover failed - initiating rollback
      ansible.builtin.debug:
        msg: "CUTOVER FAILED - Rolling back to Chef"

    - name: Re-enable Chef client
      ansible.builtin.service:
        name: chef-client
        state: started
        enabled: true

    - name: Run Chef client
      ansible.builtin.command:
        cmd: chef-client

    - name: Log rollback
      ansible.builtin.lineinfile:
        path: /var/log/ansible-cutover.log
        line: "Rollback executed: {{ ansible_date_time.iso8601 }}"
        create: true

    - name: Notify team
      ansible.builtin.debug:
        msg: "ALERT: Cutover failed and rolled back. Check logs."
```

**Execution:**

```bash
# Dry run first
ansible-playbook cutover.yml --check -i inventory/production

# Execute cutover
ansible-playbook cutover.yml -i inventory/production \
  -e "required_roles=[webserver,database,monitoring]" \
  -e "critical_services=[nginx,postgresql,myapp]" \
  -e "app_port=8080" \
  --diff
```

### Post-Cutover Validation

**Immediate (0-4 hours):**

```bash
# Service health checks
ansible all -i inventory/production -m service_facts
ansible all -i inventory/production -m shell -a "systemctl list-units --failed"

# Application health
ansible webservers -i inventory/production -m uri \
  -a "url=http://localhost/health status_code=200"

# Log analysis
ansible all -i inventory/production -m shell \
  -a "grep -i error /var/log/syslog | tail -20"

# Compare states
ansible all -i inventory/production -m setup > ansible_state.json
# Compare with pre-cutover Chef state
```

**Extended (4-24 hours):**

```bash
# Monitor resource usage
ansible all -m shell -a "top -b -n 1 | head -20"
ansible all -m shell -a "df -h"
ansible all -m shell -a "free -m"

# Check for configuration drift
ansible-playbook validate_configuration.yml --check --diff

# Run full test suite
ansible-playbook test_suite.yml -i inventory/production
```

---

## Monitoring & Alerting

### Deployment Monitoring

**Prometheus Metrics:**

```yaml
---
- name: Expose deployment metrics
  hosts: localhost
  tasks:
    - name: Record deployment start
      community.general.prometheus:
        name: ansible_deployment_start
        value: 1
        labels:
          playbook: "{{ ansible_play_name }}"
          environment: production

    - name: Include deployment tasks
      ansible.builtin.include_tasks: deploy.yml
      register: deploy_result

    - name: Record deployment success
      community.general.prometheus:
        name: ansible_deployment_success
        value: 1
        labels:
          playbook: "{{ ansible_play_name }}"
          environment: production
      when: deploy_result is succeeded

    - name: Record deployment failure
      community.general.prometheus:
        name: ansible_deployment_failure
        value: 1
        labels:
          playbook: "{{ ansible_play_name }}"
          environment: production
      when: deploy_result is failed
```

**Grafana Dashboard:**

```json
{
  "dashboard": {
    "title": "Ansible Deployments",
    "panels": [
      {
        "title": "Deployment Success Rate",
        "targets": [
          {
            "expr": "rate(ansible_deployment_success[5m]) / rate(ansible_deployment_start[5m])"
          }
        ]
      },
      {
        "title": "Failed Deployments",
        "targets": [
          {
            "expr": "ansible_deployment_failure"
          }
        ]
      }
    ]
  }
}
```

### Integration with Existing Monitoring

**Send alerts to Slack:**

```yaml
---
- name: Notify on deployment
  hosts: localhost
  tasks:
    - name: Send Slack notification
      community.general.slack:
        token: "{{ slack_token }}"
        channel: "#deployments"
        msg: |
          [DEPLOY] Deployment started
          Playbook: {{ ansible_play_name }}
          Environment: {{ environment }}
          Initiated by: {{ ansible_user_id }}
```

---

## Rollback Procedures

### Automated Rollback

```yaml
---
- name: Rollback Deployment
  hosts: app_servers

  vars:
    previous_version: "{{ lookup('file', '/tmp/previous_version.txt') }}"

  tasks:
    - name: Stop current version
      ansible.builtin.service:
        name: myapp
        state: stopped

    - name: Restore previous version
      ansible.builtin.copy:
        src: "/opt/backups/myapp-{{ previous_version }}.tar.gz"
        dest: /opt/myapp/
        remote_src: true

    - name: Extract backup
      ansible.builtin.unarchive:
        src: "/opt/myapp/myapp-{{ previous_version }}.tar.gz"
        dest: /opt/myapp/
        remote_src: true

    - name: Start previous version
      ansible.builtin.service:
        name: myapp
        state: started

    - name: Verify rollback
      ansible.builtin.uri:
        url: http://localhost:8080/health
        status_code: 200
```

---

## Deployment Checklist

### Pre-Deployment

- [ ] Code reviewed and approved
- [ ] Tests passing in CI/CD
- [ ] Staging deployment successful
- [ ] Rollback plan documented
- [ ] Maintenance window scheduled
- [ ] Stakeholders notified
- [ ] Monitoring configured
- [ ] Backup completed

### During Deployment

- [ ] Enable detailed logging
- [ ] Announce maintenance start
- [ ] Execute deployment playbook
- [ ] Monitor for errors
- [ ] Validate services
- [ ] Run smoke tests
- [ ] Check monitoring dashboards

### Post-Deployment

- [ ] Announce completion
- [ ] Monitor for 4+ hours
- [ ] Verify metrics
- [ ] Check logs for errors
- [ ] Validate functionality
- [ ] Update documentation
- [ ] Conduct retrospective

---

## Best Practices

### Do's [YES]

1. **Test in Lower Environments**: Always deploy to dev/staging first
2. **Use Check Mode**: Validate with `--check` before actual run
3. **Monitor Continuously**: Watch metrics during and after deployment
4. **Automate Rollbacks**: Have automated rollback procedures
5. **Communicate Clearly**: Keep stakeholders informed
6. **Version Everything**: Tag releases, track versions
7. **Document Changes**: Maintain deployment logs
8. **Practice Rollbacks**: Test rollback procedures regularly

### Don'ts [NO]

1. **Don't Deploy Fridays**: Avoid end-of-week deployments
2. **Don't Skip Testing**: Always test before production
3. **Don't Deploy Without Backup**: Always have rollback plan
4. **Don't Ignore Warnings**: Address all warnings before deploying
5. **Don't Deploy During Peak**: Use maintenance windows
6. **Don't Deploy Alone**: Have team support available
7. **Don't Skip Validation**: Always run post-deployment checks

---

## Troubleshooting

### Deployment Failures

**Symptom**: Playbook fails partway through

**Solution**:
```bash
# Check where it failed
ansible-playbook deploy.yml --start-at-task="Task that failed"

# Run in step mode
ansible-playbook deploy.yml --step

# Increase verbosity
ansible-playbook deploy.yml -vvv
```

### Service Won't Start After Deployment

**Symptom**: Application service fails to start

**Solution**:
```bash
# Check service status
ansible all -m systemd -a "name=myapp state=status"

# Check logs
ansible all -m shell -a "journalctl -u myapp -n 50"

# Validate configuration
ansible all -m shell -a "myapp --validate-config"
```

### Configuration Drift Detected

**Symptom**: Configuration differs from expected state

**Solution**:
```bash
# Identify differences
ansible-playbook deploy.yml --check --diff

# Re-run configuration
ansible-playbook deploy.yml --tags=config

# Reset to known state
ansible-playbook deploy.yml --force
```

---

## Next Steps

**Complete the migration:**

1. **[Review Conversion Guide](conversion.md)** - Ensure conversions are accurate
2. **[Assessment Guide](assessment.md)** - Validate complexity assessment
3. **[Examples](../user-guide/examples.md)** - Review deployment examples

**Explore automation:**

- [AWX Documentation](https://docs.ansible.com/ansible-tower/)
- [Ansible Best Practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html)
- [GitOps Workflows](https://www.gitops.tech/)

!!! success "Production Ready"
    With proper planning, testing, and monitoring, your Ansible deployment will be smooth and reliable. Start small, automate incrementally, and scale with confidence.
