Skip to content

Ansible Bootstrap — NHC EC2 Instances

Ansible is used for one-time instance provisioning after tofu apply creates the EC2s. It installs Docker, the deploy user, GitLab Runner, and hardens the OS. App deployments from this point forward are handled by GitLab CI/CD via the GitLab Runner.

Architecture

tofu apply          → EC2 instances exist (minimal user_data: swap only)
ansible-playbook    → Docker, deploy user, GitLab Runner, OS hardening
GitLab CI/CD        → app deploys on every push (GitLab Runner executes)

Ansible connects via AWS SSM Session Manager — no SSH port required, no bastion host. Every session is logged in CloudTrail.

Prerequisites

1. SSO login

aws sso login --profile nhc-prod

2. Build the Ansible Docker image (first time only)

docker compose build ansible

This installs: Python 3.12, AWS CLI v2, session-manager-plugin, Ansible, and all required Galaxy collections (amazon.aws, community.aws, community.docker).

3. Update the inventory with instance IDs

After tofu apply, get the instance IDs:

docker compose run --rm tofu -chdir=tofu/accounts/nhc output ec2_app_instance_id
docker compose run --rm tofu -chdir=tofu/accounts/nhc output ec2_django_instance_id

Update ansible/inventory/nhc.yml:

nhc-app:
  ansible_aws_ssm_instance_id: "i-0abc123..."   # ec2_app_instance_id

nhc-django:
  ansible_aws_ssm_instance_id: "i-0def456..."   # ec2_django_instance_id

Running the Bootstrap

All instances

docker compose run --rm ansible playbooks/nhc-bootstrap.yml

Single host group

# App server only (WP CMS, Gatsby, Astro builds)
docker compose run --rm ansible playbooks/nhc-bootstrap.yml --limit app_servers

# Django server only
docker compose run --rm ansible playbooks/nhc-bootstrap.yml --limit django_servers

Dry run (check mode)

docker compose run --rm ansible playbooks/nhc-bootstrap.yml --check

What Gets Installed

common role (both instances)

Item Detail
System packages sudo, vim, curl, git, unzip, ca-certificates, htop, ufw, fail2ban
Swap 4GB swapfile at /swapfile, vm.swappiness=10
Deploy user deploy (UID 1001), member of sudo and docker groups
Security Root SSH login disabled, fail2ban enabled

docker role (both instances)

Item Detail
Docker CE Latest stable from Docker apt repo
Docker Compose plugin Installed alongside Docker CE
GitLab Runner Registered to nova-infra GitLab group
App directory /home/deploy/apps/ — working directory for all Docker Compose stacks

GitLab Runner Registration

Runner registration requires a token from GitLab. This is stored in SSM and injected at registration time — never hardcoded.

Fetch the token from SSM (after it has been stored there):

aws ssm get-parameter \
  --profile nhc-prod \
  --name /nhc/gitlab/runner_token \
  --with-decryption \
  --query Parameter.Value \
  --output text

Then register manually on the instance (via SSM session) or add a registration task to the Ansible role once the token is in SSM.

Re-running After Changes

The playbook is idempotent — safe to re-run at any time to correct drift or apply role changes:

docker compose run --rm ansible playbooks/nhc-bootstrap.yml

Secrets on Instances

.env files with plaintext secrets are not used. All secrets are stored in AWS SSM Parameter Store as SecureString (encrypted with the NHC KMS CMK) and fetched at container startup.

See SSM Secrets Guide for how to store and consume secrets.