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.