BLOG // [DEVOPS] Sometimes Bash scripts are better than Ansible
Sometimes Bash scripts are better than Ansible

Sometimes Bash scripts are better than Ansible

Comparing Bash and Ansible in DevOps: when to use Bash scripts, when to use Ansible? Code samples for deploy + restart service on both.

DEVOPS BASH

Bash vs Ansible — When to choose which?

In the Bash series, we’ve seen how powerful Bash is for automation. But in DevOps, Ansible is also a popular choice for infrastructure automation. So when should you use Bash, and when should you use Ansible?

This isn’t a question of “which is better” but “which is more suitable” for a specific situation. This post will provide an objective comparison with practical code samples to help you make the right choice.


Overview comparison

CriteriaBashAnsible
Learning curveLow — everyone knows itMedium — need to learn YAML, modules
ReadabilityVery easy — plain textEasy — clear YAML
IdempotentNo — need manual implementationYes — built-in
ReusabilityMedium — source/functionsHigh — roles, modules
InventoryManual — file or arrayBuilt-in — dynamic inventory
TestingHard — need mockingEasy — molecule
DebugEasy — bash -xMedium — verbose mode
DependencyNoneRequires Python, SSH
Remote executionManual SSHBuilt-in
Best forQuick scripts, small tasksComplex infrastructure, multi-server

When to use Bash?

Bash is suitable when:

  • Simple scripts, running locally — backup, cleanup, health checks
  • Quick debuggingbash -x shows each execution line
  • No dependencies needed — Bash is available on every Linux server
  • Small team, need flexibility — don’t want to set up Ansible infrastructure
  • On-call situations — need quick fixes, no time to learn playbooks

Code sample: Deploy with Bash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env bash
# ==============================================================================
# deploy.sh — Deploy application via Bash
# ==============================================================================

set -euo pipefail

readonly APP_NAME="myapp"
readonly DEPLOY_DIR="/opt/${APP_NAME}"
readonly SERVICE_NAME="${APP_NAME}.service"
readonly SERVERS=("web-01" "web-02" "web-03")
readonly SSH_KEY="${HOME}/.ssh/deploy_key"

log() { echo "[$(date '+%H:%M:%S')] $1"; }

deploy_server() {
    local server="$1"
    log "Deploying to ${server}..."

    # Pull code
    ssh -i "$SSH_KEY" "deploy@${server}" \
        "cd ${DEPLOY_DIR} && git pull origin main"

    # Restart service
    ssh -i "$SSH_KEY" "deploy@${server}" \
        "sudo systemctl restart ${SERVICE_NAME}"

    # Health check
    local status
    status=$(ssh -i "$SSH_KEY" "deploy@${server}" \
        "curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health")

    if [[ "$status" == "200" ]]; then
        log "${server}: OK"
    else
        log "${server}: FAILED (status: ${status})"
        return 1
    fi
}

# Deploy parallel (max 3)
for server in "${SERVERS[@]}"; do
    deploy_server "$server" &
    # Limit concurrent deployments
    while (( $(jobs -r | wc -l) >= 3 )); do
        sleep 0.5
    done
done

wait
log "Deployment completed"

When to use Ansible?

Ansible is suitable when:

  • Complex infrastructure — many servers, many roles
  • Need idempotency — run again without unintended changes
  • Large team, need conventions — clear YAML, easy to review
  • Need audit trail — Ansible logs each task
  • Multi-environment — dev, staging, production with separate inventories

Code sample: Deploy with Ansible

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# deploy.yml
---
- name: Deploy application
  hosts: webservers
  become: yes
  vars:
    app_name: myapp
    deploy_dir: /opt/myapp
    service_name: myapp.service

  tasks:
    - name: Pull latest code
      git:
        repo: "https://github.com/org/myapp.git"
        dest: "{{ deploy_dir }}"
        version: main
      notify: Restart service

    - name: Ensure service is running
      systemd:
        name: "{{ service_name }}"
        state: started
        enabled: yes

    - name: Wait for health check
      uri:
        url: "http://localhost:8080/health"
        status_code: 200
      retries: 10
      delay: 5

  handlers:
    - name: Restart service
      systemd:
        name: "{{ service_name }}"
        state: restarted
1
2
3
4
5
6
7
8
# Run playbook
ansible-playbook -i inventory/hosts deploy.yml

# Dry run (check mode)
ansible-playbook -i inventory/hosts deploy.yml --check

# Limit to specific server
ansible-playbook -i inventory/hosts deploy.yml --limit web-01

Same task comparison

Task: Restart service if config changes

Bash:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env bash
# Check file changes and restart service

CONFIG_FILE="/etc/myapp/config.yml"
BACKUP_FILE="/tmp/config_backup"

current_hash=$(md5sum "$CONFIG_FILE" | awk '{print $1}')

if [[ -f "$BACKUP_FILE" ]]; then
    old_hash=$(cat "$BACKUP_FILE")
else
    old_hash=""
fi

if [[ "$current_hash" != "$old_hash" ]]; then
    echo "Config changed, restarting service..."
    sudo systemctl restart myapp.service
    echo "$current_hash" > "$BACKUP_FILE"
else
    echo "Config unchanged, skipping restart"
fi

Ansible:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
- name: Restart service if config changed
  hosts: webservers
  tasks:
    - name: Copy config file
      copy:
        src: files/config.yml
        dest: /etc/myapp/config.yml
      register: config_result

    - name: Restart service
      systemd:
        name: myapp.service
        state: restarted
      when: config_result.changed

Analysis

BashAnsible
Lines of code~15 lines~12 lines
ReadabilityVery easyEasy
IdempotentNo — need hash trackingYes — register + when
RemoteNeed SSH loopBuilt-in

Team maturity is the deciding factor

Ansible needs discipline

Ansible is powerful but requires:

  • Clear conventions — consistent task and variable naming
  • Serious code review — YAML is easy to write but easy to mess up
  • Testing — use molecule or Terratest
  • Documentation — every role should have a README

Without these, playbooks become “YAML spaghetti” — harder to debug than Bash.

Bash suits flexible teams

Bash is suitable when:

  • Small team (< 5 people) — no need for complex processes
  • Need quick fixes — on-call situations
  • Short scripts — under 200 lines
  • No dependencies wanted — just Bash and SSH

Combining both

In practice, many teams use both:

TaskUse what
Quick fix, on-callBash
Simple deploymentBash
Infrastructure provisioningAnsible
Configuration managementAnsible
Simple cron jobsBash
Multi-server orchestrationAnsible

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Bash script calling Ansible playbooks
#!/usr/bin/env bash
set -euo pipefail

echo "Running infrastructure setup..."
ansible-playbook -i inventory/prod setup.yml

echo "Deploying application..."
ansible-playbook -i inventory/prod deploy.yml

echo "Running post-deploy checks..."
ansible-playbook -i inventory/prod health-check.yml

echo "All done!"

Implementation notes

  • Don’t glorify tools: Both Bash and Ansible are tools. Choose the right tool for the task, not the “trend”.
  • Team review: Whether using Bash or Ansible, code review is important. Bash needs security checks, Ansible needs idempotency checks.
  • Documentation: Bash scripts should have --help and comments. Ansible roles should have README and variable docs.
  • Testing: Bash is harder to test, but you should still test critical scripts. Ansible has molecule for testing.
  • Progressive adoption: Start with Bash when the team is small. When the team grows and needs structure, gradually migrate to Ansible.

Conclusion

Bash and Ansible are not competitors but complementary tools. Bash excels at quick scripts, debugging, and situations requiring flexibility. Ansible excels at complex infrastructure, multi-server management, and teams needing conventions.

Choose the tool that fits the task and your team’s maturity level. Don’t use Ansible for a 5-line Bash task, and don’t use Bash for complex infrastructure requiring idempotency.

The most important thing is to understand the strengths and weaknesses of each tool to make informed decisions in your DevOps workflow.

RESPONSES & DISCUSSION