Skip to content

About

Ansible + Terraform project for easy deployment of applications on AWS with robust dry run and emulation before production deployment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

184 Commits

Folders and files

Repository files navigation

Infrastructure Project

Ansible + Terraform project for easy deployment of applications on AWS

Table of Contents


1. Full System Deployment

This infrastructure is designed for fully automated deployment via CI/CD. Manual provisioning is discouraged.

Automated Deployment (Recommended)

Push to master. The GitHub Actions workflow will automatically:

  1. Provision AWS infrastructure via Terraform and configure SSH credentials (ansible/playbooks/orchestrate.yml).
  2. Validate the configuration by deploying against the CI runner itself as a smoke test.
  3. Deploy the validated configuration to the production server using the auto-generated inventory.

Manual Deployment (Local Testing Only)

If you must run the deployment manually, replicate the pipeline steps in order:

# Step 1: Provision infrastructure and SSH setup
ansible-playbook \
  -i ansible/inventory/local.ini \
  --vault-password-file secrets/vault \
  ansible/playbooks/orchestrate.yml

# Step 2: Validate configuration locally
ansible-playbook \
  -i ansible/inventory/local.ini \
  --vault-password-file secrets/vault \
  ansible/playbooks/playbook.yml

# Step 3: Deploy to production
ansible-playbook \
  -i ansible/inventory/production.ini \
  --vault-password-file secrets/vault \
  ansible/playbooks/playbook.yml

Note: Terraform automatically generates ansible/inventory/production.ini during the orchestration stage. Do not edit this file manually — changes will be overwritten on the next run.


2. Adding a New Application

Applications are strictly managed by Docker Compose. Do not use Ansible to deploy applications.

  1. Define the container in docker-compose.yml (repo root).

  2. Enforce local binding — the container's port must bind only to localhost, never directly to the internet:

    ports:
      - "127.0.0.1:8080:80"  # Correct
      # - "8080:80"          # WRONG — exposes to the public internet
  3. Configure the reverse proxy by adding the service to ansible/inventory/group_vars/webservers.yml. Ansible generates the Nginx reverse-proxy config for you on the next run:

    # ansible/inventory/group_vars/webservers.yml
    services:
      - name: gitea
        port: 3000
      - name: newapp      # <-- add your new app here
        port: 8080

Option 1 — Pull from Galaxy at install time

Simplest option. CI resolves the role from requirements.yml on every run.

  1. Search first: find a well-maintained role on Ansible Galaxy (e.g. geerlingguy.security).

  2. Add to requirements, pinned to an exact version:

    # ansible/requirements.yml
    roles:
      - name: geerlingguy.redis
        version: "1.5.0"
  3. Enable it in the roles: list of ansible/playbooks/playbook.yml.

  4. Configure it via role variables in ansible/inventory/group_vars/webservers.yml.

Never omit version: — an unpinned role resolves to whatever is newest at install time, which breaks the "a second run must be idempotent and reproducible" guarantee in section 3.

Option 2 — Vendor the role into the repo (preferred for control and reliability)

cd ansible/
ansible-galaxy install geerlingguy.firewall,2.7.0 -p ./roles/
git add roles/geerlingguy.firewall
git commit -m "vendor geerlingguy.firewall @ 2.7.0"

Rules for vendored roles:

  • Keep the license file. Only vendor MIT / Apache-2.0 / BSD roles (or anything else permissive) and leave the LICENSE file in place — that satisfies the attribution requirement. Don't vendor GPL or unlicensed roles without checking terms first.
  • Record provenance. Add a one-line note (in a VENDORED.md next to the role, or a comment in requirements.yml) with the source repo, version, and date pulled, so a future update is a deliberate, diffable act rather than a guess.
  • Bumping versions is a PR, not a background refresh. Re-run the install command, git diff the result, and review it like any other dependency bump before merging.

Vendored roles still go in ansible/roles/, alongside anything pulled via requirements.yml — there's no separate vendor/ directory in Ansible's model; a committed role directory is the vendored copy.


3. Core Development Standards

  1. Strict separation of concerns (Ansible vs. Docker). Ansible is for host-level configuration only: firewall, OS updates, user management, installing Docker/Nginx, and low-level network services like WireGuard. Docker Compose is for application services (Gitea, databases, web apps).
  2. No public container ports. Backend application ports must never be exposed directly to the internet. Containers bind strictly to 127.0.0.1 and route through the Nginx reverse proxy.
  3. Centralized firewall management. Do not write custom iptables or ufw tasks inside other roles. To open a port for a non-HTTP service, append it to firewall_allowed_tcp_ports / firewall_allowed_udp_ports in ansible/inventory/group_vars/webservers.yml.
  4. Use handlers for service restarts. Never call the service module directly to restart something in tasks/main.yml. Use notify: <Handler Name> and define the restart in the role's handlers/main.yml.
  5. No hardcoded variables. Never hardcode IPs, domains, or passwords in tasks or templates. Define them in defaults/main.yml or pass them via CI/CD environment variables.
  6. Strict idempotency. Every run must be safely repeatable — a second run must produce changed=0 across all tasks. Use state-managing modules, or creates / removes / changed_when for any raw shell commands.
  7. Centralized configuration. All role variables and application settings belong in ansible/inventory/group_vars/webservers.yml. Playbook-level vars: are for computed, play-specific values only.

4. CI/CD Secret Management

Automated deployments via GitHub Actions require AWS credentials and an Ansible Vault password. Every other secret (SSH keys, database passwords, etc.) lives inside the encrypted vault, not as a raw GitHub secret.

Creating the vault password file

mkdir -p secrets
echo "YOUR_STRONG_VAULT_PASSWORD" > secrets/vault

This file must never be committed.

Managing the encrypted vault

The encrypted vault lives at ansible/inventory/group_vars/all/vault.yml and is committed to the repo in encrypted form.

Create a new vault from scratch:

ansible-vault create ansible/inventory/group_vars/all/vault.yml --vault-password-file secrets/vault

Add secrets as plain YAML inside the editor that opens:

vault_aws_access_key_id: "AKIA..."
vault_aws_secret_access_key: "..."
vault_ssh_private_key: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  ...
  -----END OPENSSH PRIVATE KEY-----
vault_gitea_db_password: "..."
vault_mysql_root_password: "..."

Save and close — the file is encrypted on disk automatically.

Edit an existing vault:

ansible-vault edit ansible/inventory/group_vars/all/vault.yml --vault-password-file secrets/vault

View vault contents without editing:

ansible-vault view ansible/inventory/group_vars/all/vault.yml --vault-password-file secrets/vault

Encrypt a single string inline (for adding one secret without opening the full vault):

ansible-vault encrypt_string --vault-password-file secrets/vault 'my_secret_value' --name 'vault_my_variable'

Copy the output directly into vault.yml.

Git ignore

secrets/ must be ignored — nothing inside it is ever committed:

echo "secrets/" >> .gitignore
git status  # secrets/ should NOT appear

Pushing secrets to GitHub Actions

Only three secrets are needed in GitHub Actions. The committed, encrypted vault.yml is decrypted at runtime using VAULT_PASSWORD.

# AWS credentials
echo "$AWS_ACCESS_KEY_ID" | gh secret set AWS_ACCESS_KEY_ID
echo "$AWS_SECRET_ACCESS_KEY" | gh secret set AWS_SECRET_ACCESS_KEY

# Vault password (unlocks all other secrets at deploy time)
cat secrets/vault | gh secret set VAULT_PASSWORD

⚠️ Note: Use a dedicated SSH deploy key (e.g. id_ed25519_deploy) rather than your personal SSH key. Store its private key content inside vault.yml as vault_ssh_private_key — not as a separate GitHub secret.

About

Ansible + Terraform project for easy deployment of applications on AWS with robust dry run and emulation before production deployment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages