Themelio compatibility reference. This page describes the retained Themelio implementation or its historical design. Check the parallel-operation guide before applying it to the current platform.
DD-0014: GitLab CI Runner EC2 Instance — Manual Operator Steps
Related ADR: ADR-0014: GitLab CI Runner on Dedicated AWS EC2 Instance
Status: Draft
Date: 2026-06-03
Authors: Thanasis Katsadas
Overview
This document is the operator runbook for provisioning and securing the EC2 instance decided in ADR-0014. It covers all manual steps performed in the AWS console: creating a dedicated VPC, importing the SSH key pair, allocating an Elastic IP, creating a security group, launching the instance with the correct configuration, and verifying SSH connectivity. Once these steps are complete, the instance is ready for Ansible onboarding as described in DD-0015.
Goals and Non-Goals
Goals
- Allocate and configure all required AWS resources (key pair, Elastic IP, security group, EC2 instance) manually via the AWS console in the runner account.
- Leave the instance reachable via SSH from GRNET/NTUA VPN-connected workstations and ready for Ansible.
Non-Goals
- Ansible provisioning, Docker engine setup, NVMe mount configuration — covered in DD-0015.
- GitLab runner installation and registration — covered in DD-0015.
- Terraform automation of any of these steps.
Design
Prerequisites
Before starting, the operator must have:
- AWS console access to the runner AWS account (separate from the main Themelio account).
- VPN active — GRNET or NTUA VPN must be connected on the operator workstation.
- Fleet SSH public key available locally at
~/.ssh/pharos-admin-key.pub. If not present, runmake fetch-keysfromdevops/ansible/to download it from AWS Secrets Manager (pharos-cloud/admin-ssh-key-public).
Step 1: Create the dedicated VPC
- VPC → Create VPC → select "VPC and more" (creates the subnet, internet gateway, and route table automatically).
- Name tag:
pharos-gitlab-ci-runner-vpc. - IPv4 CIDR:
10.0.0.0/24. - Tenancy: Default.
- Number of AZs: 1. Public subnets: 1. Private subnets: 0.
- NAT gateways: None.
- Tags:
Project = themelio. - Create VPC.
- Verify the internet gateway: VPC → Internet Gateways — confirm one is attached to
pharos-gitlab-ci-runner-vpc. - Verify the route table: VPC → Route Tables → select the route table associated with the public subnet → confirm a route
0.0.0.0/0 → igw-xxxxxxxxexists.
Step 2: Import the fleet SSH key pair
All fleet nodes use the pharos-admin-key key pair. Because the runner lives in a separate AWS account, the public key must be imported there before the instance can be launched.
- In the runner AWS account: EC2 → Key Pairs → Import key pair.
- Name:
pharos-admin-key. - Paste the contents of
~/.ssh/pharos-admin-key.pub. - Import.
Step 3: Allocate the Elastic IP
- EC2 → Elastic IPs → Allocate Elastic IP address.
- Network border group:
eu-central-1. - Tags:
Name = pharos-gitlab-ci-runner-eip,Project = themelio. - Record the allocated IP address — this value is entered into
inventory/runner/hosts.ymlduring Ansible onboarding (DD-0015).
Step 4: Create the Security Group
-
EC2 → Security Groups → Create security group.
-
Name:
pharos-gitlab-ci-runner-sg. -
Description:
GitLab CI runner — SSH from VPN only. -
VPC: select the VPC in the runner account where the instance will be launched.
-
Inbound rules:
-
Outbound rules: keep the default (allow all — the runner needs outbound HTTPS to
gitlab.grnet.gr, Docker registries, and OS package mirrors). -
Tags:
Name = pharos-gitlab-ci-runner-sg,Project = themelio.
Step 5: Launch the EC2 instance
-
EC2 → Launch Instances.
-
Name:
pharos-gitlab-ci-runner-1. -
AMI: Search the AWS Marketplace for the official Debian 12 (Bookworm) AMI in
eu-central-1. Verify the publisher is "Debian". Note the AMI ID used. -
Instance type:
c6id.4xlarge. -
Key pair:
pharos-admin-key(imported in Step 2). -
Network settings:
- VPC: same VPC as the security group above.
- Subnet: a public subnet with an internet gateway route. The instance needs outbound internet access to reach
gitlab.grnet.grand Docker registries. - Auto-assign public IP: Disable — the Elastic IP from Step 3 replaces this.
- Security group:
pharos-gitlab-ci-runner-sg(Step 4).
-
Storage: root volume only. The default gp3 volume (8–20 GB) is sufficient. The local NVMe (~950 GB) is provided automatically by the
c6id.4xlargeinstance type — no additional EBS volume is needed. -
Advanced details → User data: paste the following cloud-init configuration. This creates the
adminuser with passwordless sudo and the fleet SSH key on first boot, consistent with all other fleet nodes. -
Tags:
Name = pharos-gitlab-ci-runner-1,Project = themelio,Environment = production. -
Launch instance.
Step 6: Associate the Elastic IP
- EC2 → Elastic IPs → select the EIP allocated in Step 3.
- Actions → Associate Elastic IP address.
- Resource type: Instance. Select
pharos-gitlab-ci-runner-1. - Confirm. The EIP is now permanently bound to the instance and persists across stop/start/reboot cycles.
Step 7: Purchase the Reserved Instance
The instance launched in Step 5 starts as on-demand. The 1-year all-upfront reservation is a separate billing action:
- EC2 → Reserved Instances → Purchase Reserved Instances.
- Platform:
Linux/UNIX. Instance type:c6id.4xlarge. Region: EU (Frankfurt). - Term: 1 year. Offering class: Standard. Payment option: All Upfront.
- Confirm purchase (~$4,803). AWS automatically applies the reservation to any matching running
c6id.4xlargeinstance in the account.
Step 8: Verify SSH access
From a workstation with GRNET or NTUA VPN active:
Once logged in, confirm:
If SSH fails, check:
- The operator workstation is on GRNET or NTUA VPN.
- The security group inbound rules match exactly the CIDRs in Step 4.
- The EIP is associated with the instance (Step 6).
- The instance has finished booting (allow ~60 seconds after launch).
Open Questions
Q1: GRNET and NTUA VPN CIDR ranges
Resolved: 195.251.29.0/25 (GRNET), 147.102.4.167/32, 147.102.3.0/24, 147.102.4.0/24 (NTUA) — sourced from inventory/{env}/group_vars/ssl-services.yml.
Q2: Runner AWS account ownership and IAM access
Who owns the runner AWS account and which operators have console access to perform these steps?
Q3: SSH key strategy
Resolved: Reuse pharos-admin-key. Public key at ~/.ssh/pharos-admin-key.pub, downloaded via make fetch-keys from AWS Secrets Manager (pharos-cloud/admin-ssh-key-public). Must be imported into the runner account (Step 2) before instance launch.
References
- ADR-0014: GitLab CI Runner on Dedicated AWS EC2 Instance
- AWS EC2 c6id instance family
- AWS Elastic IP Addresses
- Debian 12 on AWS Marketplace
- cloud-init user configuration
Page source
Source snapshot · Revision f928834c8.