Why I Reach for Ansible on Every Project (And How I Set It Up)

Why I Reach for Ansible on Every Project (And How I Set It Up)

I've been using Ansible for years now, and it's become my go-to tool for literally everything infrastructure-related. Whether I'm spinning up a new side project or setting up production systems, Ansible is always my first reach. Here's why that is, and exactly how I set it up for new projects.

The Problem with Manual Server Management

A few years back, I wrote about my initial happiness with Ansible when I first discovered it. Those reasons still hold true today, but my usage has evolved significantly.

The core issue hasn't changed: when you're managing servers manually, you lose track of what you've done. You SSH in, change a firewall rule, tweak some nginx config, install a package, and six months later you have no idea how your server actually works. Worse, when you need to replicate that setup, you're starting from scratch, trying to remember what worked.

I used to handle this with a collection of bash scripts and Makefiles. It worked, sort of, but it was fragile. Miss one step, get the order wrong, or have a command fail halfway through, and you're debugging a half-configured server. Not fun.

Ansible vs Terraform: Different Tools for Different Jobs

Before we go further, it's worth a brief detour discussing Ansible vs Terraform. People often ask about the differences, and the truth is they solve different problems.

Terraform excels at infrastructure provisioning - spinning up cloud resources, configuring networks, managing DNS records. It's declarative and handles the "what infrastructure should exist" question brilliantly.

Ansible shines at configuration management - what happens after the server exists. Installing packages, configuring services, deploying applications, managing users. It's procedural and handles the "how should this server be configured" question.

In my workflow, I could use Terraform to create the DigitalOcean droplet, but I always use Ansible to configure what runs on it. They complement each other rather than compete. For small projects, I often skip Terraform entirely and just spin up servers manually, then let Ansible handle everything else.

Why Ansible Wins Every Time

Idempotency is everything. Run an Ansible playbook once, run it a hundred times - same result. This is what transforms server management from a stressful guessing game into a predictable process.

No agents. Unlike SaltStack or Puppet, there's nothing to install or maintain on your servers. Ansible just needs SSH access, which you already have. One less moving part means one less thing to break.

Readability. YAML is verbose, but it's also incredibly clear what's happening. When I revisit a playbook six months later, I can immediately understand what it does and why.

Incremental adoption. You don't need to rewrite everything at once. Start with one task, then another. Build up your automation gradually.

My Current Ansible Workflow

These days, my typical setup involves a small VPS on DigitalOcean for side projects, or dedicated instances for anything that needs more resources. The stack is pretty consistent: Go services on the backend, Vue or React frontends, Postgres for data, Redis for caching, and Caddy for the web server.

Here's my process when starting a new project:

1. Project Structure

I start with a standard Ansible structure that I've refined over years of projects. These days, I'll often use AI to generate the initial scaffolding - I give it a prompt like "create me a standard Ansible project setup with three environments and include roles for Go, Postgres, Redis, and Caddy" - but I always review and adjust it to match my preferred patterns. It's faster than typing it out manually, and since I know what good structure looks like, I can quickly spot and fix anything that's off.

ansible/
├── inventory/
│   ├── dev/
│   │   ├── hosts
│   │   └── group_vars/
│   ├── staging/
│   │   ├── hosts
│   │   └── group_vars/
│   └── prod/
│       ├── hosts
│       └── group_vars/
├── playbooks/
│   ├── setup.yml
│   ├── deploy.yml
│   └── security.yml
├── roles/
│   ├── common/
│   ├── caddy/
│   ├── postgres/
│   └── app/
└── vault/
    ├── dev.yml
    ├── staging.yml
    └── prod.yml

2. Environment Management

This is where things get interesting, and where I've found one of Ansible's most powerful features: the ability to use variables defined in inventory files.

Instead of hardcoding values or passing them as command-line arguments, I structure my environments so that each one has its own inventory folder with specific variables. When I run ansible-playbook -i inventory/prod playbooks/deploy.yml, Ansible automatically uses the variables defined in that inventory structure.

Here's how I typically structure the inventory for a production environment:

# inventory/prod/hosts
[web]
prod-server-1 ansible_host=142.93.123.45

[db]
prod-server-1

[all:vars]
environment=production
app_port=8080
domain=myapp.com
postgres_db=myapp_prod
# inventory/prod/group_vars/all.yml
app_user: myapp
app_dir: /opt/myapp
caddy_config_dir: /etc/caddy
postgres_version: "14"
redis_maxmemory: "256mb"

# Environment-specific settings
debug_mode: false
log_level: "info"
ssl_redirect: true

The beauty of this approach is that I can use the same playbooks across all environments, but the behaviour changes based on which inventory I target. The variables defined in inventory take precedence, so I never have to worry about accidentally deploying development settings to production.

3. Secrets Management

Ansible Vault handles all the sensitive data. Each environment gets its own vault file:

ansible-vault create vault/prod.yml

These vault files contain things like:

  • Database passwords
  • API keys (Anthropic, Stripe webhook secrets, etc.)
  • SSL certificates

When I need to use these in playbooks, I reference them normally:

- name: Set environment variables
  template:
    src: app.env.j2
    dest: "{{ app_dir }}/.env"
    owner: "{{ app_user }}"
    mode: '0600'
  vars:
    stripe_secret: "{{ vault_stripe_secret }}"
    anthropic_api_key: "{{ vault_anthropic_key }}"

4. The Core Playbooks

I typically have three main playbooks:

setup.yml - Initial server configuration:

  • Creates users and SSH key setup
  • Configures firewall (UFW)
  • Hardens SSH (no root login, key-only auth)
  • Installs base packages
  • Sets up logging and monitoring

deploy.yml - Application deployment:

  • Installs Go, Node.js, or other runtime dependencies
  • Sets up Postgres and Redis
  • Configures Caddy with automatic HTTPS
  • Deploys application binaries
  • Creates systemd services
  • Manages application configuration

The deployment playbook is where I often use Ansible's stat module to check if files exist before copying or updating them. For example, before deploying a new binary, I'll check if the current version exists and back it up:

- name: Check if current binary exists
  stat:
    path: "{{ app_dir }}/{{ app_name }}"
  register: current_binary

- name: Backup current binary
  copy:
    src: "{{ app_dir }}/{{ app_name }}"
    dest: "{{ app_dir }}/{{ app_name }}.backup"
    remote_src: yes
  when: current_binary.stat.exists

This kind of safety check prevents deployments from failing if something unexpected happens with file states.

security.yml - Ongoing security maintenance:

  • Updates packages
  • Rotates log files
  • Checks for security updates
  • Validates firewall rules

A Concrete Example

Let's say I'm launching a new fintech side project tomorrow. Here's exactly what I'd do:

  1. Spin up a DigitalOcean droplet and add the IP to inventory/prod/hosts
  2. Generate the Ansible structure with AI assistance, telling it I need Postgres, Redis, Caddy, and systemd services
  3. Define my variables in the inventory:
app_name: fintech-app
app_port: 8080
domain: fintech-app.com
postgres_db: fintech_prod
  1. Set up vault secrets for database passwords, Stripe webhook secrets, and any other API keys
  2. Run the setup playbook:
ansible-playbook -i inventory/prod playbooks/setup.yml --ask-vault-pass
  1. Deploy the application:
ansible-playbook -i inventory/prod playbooks/deploy.yml --ask-vault-pass

The entire process takes about 10 minutes, and I end up with a fully configured, secure server running my application with HTTPS, proper logging, and all the security hardening in place.

It just works.

Version Control and Git Integration

One thing I've found particularly useful is keeping my Ansible playbooks in git repositories alongside the application code. This means when I clone a project, I get both the application and its deployment configuration in one go.

I structure it like this:

myproject/
├── cmd/           # Go application
├── web/           # Frontend assets  
├── ansible/       # All playbooks and configs
└── README.md

When I need to deploy, I can run the ansible playbook directly from the git repository. This ensures the deployment configuration is always in sync with the application version I'm deploying. No more wondering which version of the playbook matches which version of the app.

For production deployments, I often tag releases and run playbooks from specific tags to ensure consistency.

What I Love About This Approach

Reproducibility. If something goes wrong, I can spin up a new server and have it running identically in minutes.

Documentation. The playbooks serve as living documentation of exactly how my infrastructure is configured.

Testing. I can run the same playbooks against a local VM to test changes before applying them to production.

Consistency. Every project follows the same patterns, so switching between them is seamless.

Deployment as code. Everything from the initial server setup to application deployments is versioned and tracked.

The Evolution

Comparing this to my earlier Ansible usage, the biggest change is how I handle deployments. I used to think of Ansible primarily as a server setup tool, but now I use it for the entire application lifecycle. Instead of manual make deploycommands or SSH-ing in to restart services, everything goes through Ansible.

This has been particularly valuable for side projects where I might not touch the deployment for months. When I need to push an update, I don't have to remember the deployment procedure - it's all captured in the playbook.

What About Ansible Tower?

You might wonder about Ansible Tower (now called Red Hat Ansible Automation Platform). It's a web-based interface for managing Ansible deployments, with features like job scheduling, inventory management, and role-based access control.

For enterprise environments with multiple teams and complex workflows, Tower makes sense. But for my use case - mostly solo projects or small teams - the overhead isn't worth it. Running playbooks from the command line is fast, simple, and gives me complete control. Plus, one less service to maintain and secure.

The command line interface is actually part of what I love about Ansible. It's transparent, scriptable, and doesn't hide anything behind a GUI.

Why This Matters

In a world where everyone's talking about Kubernetes and cloud-native this and serverless that, sometimes the simple solution is the right solution. For most of my projects, a well-configured VPS with Ansible-managed deployments gives me everything I need: predictability, reproducibility, and simplicity.

Ansible scales with your needs. Start simple, add complexity only when you need it. But the foundation - codified infrastructure and deployments - pays dividends from day one.

If you're still SSH-ing into servers and running commands manually, give Ansible a try. Start with one simple playbook for one common task. You'll quickly find yourself asking, "What else can I automate with this?"

For more on my development workflow and infrastructure choices, check out:


Need help with your business?

Enjoyed this post? I help companies navigate AI implementation, fintech architecture, and technical strategy. Whether you're scaling engineering teams or building AI-powered products, I'd love to discuss your challenges.

Learn more about how I can support you.

Get practical insights weekly

Real solutions to real problems. No fluff, just production-ready code and workflows that work.
You've successfully subscribed to Kyle Redelinghuys
Great! Next, complete checkout to get full access to all premium content.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.