|
| 1 | +# Local AMI Development with pg-ami-builder |
| 2 | + |
| 3 | +This guide explains how to use `pg-ami-builder` for local AMI development and iteration. |
| 4 | + |
| 5 | + Summary |
| 6 | + |
| 7 | + | Aspect | CI/CD Workflows | pg-ami-builder | |
| 8 | + |--------------------|---------------------------------|-----------------------------------------| |
| 9 | + | AMI Creation | Packer auto-creates only | Packer auto-creates + manual create-ami | |
| 10 | + | Workflow | Linear, automated | Iterative, debuggable | |
| 11 | + | State | Stateless, ephemeral | Stateful, persistent | |
| 12 | + | Error Handling | Terminate and restart | Preserve, debug, fix, continue | |
| 13 | + | Use Case | Production releases, CI testing | Local development, iteration | |
| 14 | + | Instance Lifecycle | Always terminated | Preserved for debugging | |
| 15 | + |
| 16 | + |
| 17 | +## Prerequisites |
| 18 | + |
| 19 | +### Required Tools |
| 20 | + |
| 21 | +- AWS CLI v2 |
| 22 | +- aws-vault (for credential management) |
| 23 | +- SSM Session Manager plugin |
| 24 | +- Git |
| 25 | +- Nix |
| 26 | + |
| 27 | +### Installing SSM Session Manager Plugin |
| 28 | + |
| 29 | +**macOS:** |
| 30 | +```bash |
| 31 | +brew install --cask session-manager-plugin |
| 32 | +``` |
| 33 | + |
| 34 | +**Linux:** |
| 35 | +See [AWS documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) |
| 36 | + |
| 37 | +### AWS Permissions |
| 38 | + |
| 39 | +Your AWS user/role needs these permissions: |
| 40 | +- EC2: RunInstances, TerminateInstances, DescribeInstances, CreateTags |
| 41 | +- EC2: CreateSecurityGroup, DeleteSecurityGroup, AuthorizeSecurityGroupIngress |
| 42 | +- SSM: StartSession, DescribeSessions |
| 43 | +- EC2: CreateImage, DescribeImages (if using --create-ami) |
| 44 | + |
| 45 | +## Quick Start |
| 46 | + |
| 47 | +### Building Phase 1 |
| 48 | + |
| 49 | +```bash |
| 50 | +# Run phase 1 build (launches instance and runs packer build) |
| 51 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase1 --postgres-version 15 |
| 52 | + |
| 53 | +# If packer build fails, instance stays alive for debugging |
| 54 | +# SSH to investigate |
| 55 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- ssh |
| 56 | + |
| 57 | +# Make local changes and re-run with file sync |
| 58 | +vim ansible/playbook.yml |
| 59 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- ansible-rerun phase1 --sync-files |
| 60 | + |
| 61 | +# Cleanup when done |
| 62 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- cleanup |
| 63 | +``` |
| 64 | + |
| 65 | +### Building Phase 2 |
| 66 | + |
| 67 | +```bash |
| 68 | +# Run phase 2 with existing stage-1 AMI |
| 69 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase2 \ |
| 70 | + --source-ami ami-stage1-xyz \ |
| 71 | + --postgres-version 15 |
| 72 | +``` |
| 73 | + |
| 74 | +## Commands |
| 75 | + |
| 76 | +### build phase1 |
| 77 | + |
| 78 | +Launch EC2 instance and run phase 1 ansible playbook. |
| 79 | + |
| 80 | +```bash |
| 81 | +nix run .#pg-ami-builder -- build phase1 --postgres-version 15 [flags] |
| 82 | +``` |
| 83 | + |
| 84 | +**Flags:** |
| 85 | +- `--postgres-version` (required) - PostgreSQL major version (15, 16, 17) |
| 86 | +- `--region` - AWS region (default: us-east-1) |
| 87 | +- `--create-ami` - Create AMI on success (default: false) |
| 88 | +- `--ansible-args` - Additional ansible arguments |
| 89 | +- `--instance-type` - EC2 instance type (default: c6g.4xlarge) |
| 90 | +- `--state-file` - Custom state file path |
| 91 | + |
| 92 | +### build phase2 |
| 93 | + |
| 94 | +Launch EC2 instance from stage-1 AMI and run phase 2 ansible playbook. |
| 95 | + |
| 96 | +```bash |
| 97 | +nix run .#pg-ami-builder -- build phase2 --source-ami ami-xyz --postgres-version 15 [flags] |
| 98 | +``` |
| 99 | + |
| 100 | +**Flags:** |
| 101 | +- `--source-ami` (required) - Stage-1 AMI ID |
| 102 | +- `--postgres-version` (required) - PostgreSQL major version |
| 103 | +- `--git-sha` - Git SHA for nix packages (default: current HEAD) |
| 104 | +- Plus all flags from phase1 |
| 105 | + |
| 106 | +### ansible-rerun |
| 107 | + |
| 108 | +Re-run ansible playbook on existing instance. Optionally sync local file changes first. |
| 109 | + |
| 110 | +```bash |
| 111 | +nix run .#pg-ami-builder -- ansible-rerun phase1 [flags] |
| 112 | +``` |
| 113 | + |
| 114 | +**Flags:** |
| 115 | +- `--instance-id` - Target specific instance (default: from state file) |
| 116 | +- `--sync-files` - Sync local ansible/, scripts/, and migrations/ files before running (default: false) |
| 117 | +- `--ansible-args` - Additional ansible arguments |
| 118 | +- `--skip-tags` - Ansible tags to skip |
| 119 | +- `--region` - AWS region (default: us-east-1) |
| 120 | + |
| 121 | +**Examples:** |
| 122 | + |
| 123 | +```bash |
| 124 | +# Re-run without syncing files (use existing files on instance) |
| 125 | +nix run .#pg-ami-builder -- ansible-rerun phase1 |
| 126 | + |
| 127 | +# Re-run with local file changes |
| 128 | +nix run .#pg-ami-builder -- ansible-rerun phase1 --sync-files |
| 129 | + |
| 130 | +# Re-run with skip tags |
| 131 | +nix run .#pg-ami-builder -- ansible-rerun phase1 --skip-tags migrations |
| 132 | +``` |
| 133 | + |
| 134 | +### ssh |
| 135 | + |
| 136 | +Connect to instance via AWS SSM Session Manager (default) or EC2 Instance Connect. |
| 137 | + |
| 138 | +```bash |
| 139 | +nix run .#pg-ami-builder -- ssh [flags] |
| 140 | +``` |
| 141 | + |
| 142 | +**Flags:** |
| 143 | +- `--instance-id` - Target specific instance for SSM (default: from state file) |
| 144 | +- `--region` - AWS region for SSM (default: us-east-1) |
| 145 | +- `--aws-ec2-connect-cmd` - Full AWS EC2 Instance Connect command string |
| 146 | + |
| 147 | +**Examples:** |
| 148 | + |
| 149 | +```bash |
| 150 | +# Connect via SSM (default) |
| 151 | +nix run .#pg-ami-builder -- ssh |
| 152 | + |
| 153 | +# Connect via EC2 Instance Connect |
| 154 | +nix run .#pg-ami-builder -- ssh \ |
| 155 | + --aws-ec2-connect-cmd "aws ec2-instance-connect ssh --instance-id i-024bba2db43e4b41f --region us-east-1" |
| 156 | +``` |
| 157 | + |
| 158 | +### cleanup |
| 159 | + |
| 160 | +Terminate instance and remove associated resources. |
| 161 | + |
| 162 | +```bash |
| 163 | +nix run .#pg-ami-builder -- cleanup [flags] |
| 164 | +``` |
| 165 | + |
| 166 | +**Flags:** |
| 167 | +- `--instance-id` - Target specific instance (default: from state file) |
| 168 | +- `--force` - Skip confirmation prompt |
| 169 | + |
| 170 | +## Workflows |
| 171 | + |
| 172 | +### Workflow 1: Develop and test phase 1 changes |
| 173 | + |
| 174 | +```bash |
| 175 | +# Run phase 1 build (launches instance and runs packer build) |
| 176 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase1 --postgres-version 15 |
| 177 | + |
| 178 | +# If packer fails, instance stays up for debugging |
| 179 | +# SSH to investigate |
| 180 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- ssh |
| 181 | + |
| 182 | +# Make local changes to ansible files |
| 183 | +vim ansible/playbook.yml |
| 184 | + |
| 185 | +# Re-run with your local changes |
| 186 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- ansible-rerun phase1 --sync-files |
| 187 | + |
| 188 | +# Repeat until working, then create AMI |
| 189 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase1 --postgres-version 15 --create-ami |
| 190 | + |
| 191 | +# Cleanup |
| 192 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- cleanup |
| 193 | +``` |
| 194 | + |
| 195 | +### Workflow 2: Parallel builds for multiple postgres versions |
| 196 | + |
| 197 | +```bash |
| 198 | +# Build PG 15 |
| 199 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase1 \ |
| 200 | + --postgres-version 15 \ |
| 201 | + --state-file ~/.pg-ami-build/pg15.json |
| 202 | + |
| 203 | +# Build PG 16 in parallel |
| 204 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- build phase1 \ |
| 205 | + --postgres-version 16 \ |
| 206 | + --state-file ~/.pg-ami-build/pg16.json |
| 207 | + |
| 208 | +# SSH into PG 15 instance |
| 209 | +aws-vault exec <profile> -- nix run .#pg-ami-builder -- ssh \ |
| 210 | + --state-file ~/.pg-ami-build/pg15.json |
| 211 | +``` |
| 212 | + |
| 213 | +## Troubleshooting |
| 214 | + |
| 215 | +### SSM Connection Fails |
| 216 | + |
| 217 | +1. Check SSM agent status on the instance |
| 218 | +2. Verify instance profile has SSM permissions |
| 219 | +3. Ensure session-manager-plugin is installed |
| 220 | + |
| 221 | +### Ansible Fails |
| 222 | + |
| 223 | +The instance is kept running on failure. Check logs: |
| 224 | + |
| 225 | +```bash |
| 226 | +# SSH into instance |
| 227 | +nix run .#pg-ami-builder -- ssh |
| 228 | + |
| 229 | +# Check ansible logs |
| 230 | +sudo journalctl -u ansible-provisioner |
| 231 | +``` |
| 232 | + |
| 233 | +### State File Issues |
| 234 | + |
| 235 | +If state file references non-existent instance: |
| 236 | + |
| 237 | +```bash |
| 238 | +# Override with specific instance |
| 239 | +nix run .#pg-ami-builder -- ssh --instance-id i-xxxxx |
| 240 | + |
| 241 | +# Or clear state and start fresh |
| 242 | +rm ~/.pg-ami-build/state.json |
| 243 | +``` |
| 244 | + |
| 245 | +## Advanced Usage |
| 246 | + |
| 247 | +### Custom State Files for Parallel Builds |
| 248 | + |
| 249 | +Use `--state-file` to manage multiple builds: |
| 250 | + |
| 251 | +```bash |
| 252 | +nix run .#pg-ami-builder -- build phase1 \ |
| 253 | + --postgres-version 15 \ |
| 254 | + --state-file ~/.pg-ami-build/custom.json |
| 255 | +``` |
| 256 | + |
| 257 | +### Additional Ansible Arguments |
| 258 | + |
| 259 | +Pass custom arguments to ansible: |
| 260 | + |
| 261 | +```bash |
| 262 | +nix run .#pg-ami-builder -- build phase1 \ |
| 263 | + --postgres-version 15 \ |
| 264 | + --ansible-args="--skip-tags=migrations" |
| 265 | +``` |
| 266 | + |
| 267 | +## State File |
| 268 | + |
| 269 | +Location: `~/.pg-ami-build/state.json` |
| 270 | + |
| 271 | +The state file tracks the current build instance, allowing subsequent commands to operate on the same instance without specifying `--instance-id`. |
| 272 | + |
| 273 | +Example state: |
| 274 | +```json |
| 275 | +{ |
| 276 | + "instance_id": "i-1234567890abcdef0", |
| 277 | + "phase": "phase1", |
| 278 | + "execution_id": "1731672000-15", |
| 279 | + "region": "us-east-1", |
| 280 | + "postgres_version": "15", |
| 281 | + "timestamp": "2025-11-15T10:30:00Z", |
| 282 | + "git_sha": "abc123def456" |
| 283 | +} |
| 284 | +``` |
0 commit comments