A Bash CLI toolkit for managing AWS infrastructure — built from the AWS CLI up, with no Terraform, no CloudFormation, no console.
Every AWS resource has its own independent function. create_vpc, create_subnet, authorize_ingress, launch_bastion, ssh_webserver — each works standalone, tracks its own state locally, and composes cleanly with everything else.
create_environment is one of those compositions. It is not the point.
This grew out of a failed attempt to rebuild Phase 4 AWS infrastructure from memory. The guided lab had covered everything. The understanding hadn't stuck.
Rather than work around the gap, I stopped the next project and rebuilt from scratch — one resource at a time, no guidance, just the AWS CLI reference. The functions accumulated because the problems did.
📄 See docs/STORY.md for the full account.
Eight modules covering every layer of AWS networking and compute. Each module is independently usable.
| Need this? | Run this |
|---|---|
| One subnet | create_subnet |
| Revoke one SG rule | revoke_ingress |
| SSH into the WebServer | ssh_webserver — detects your IP, updates the SG rule, connects through the Bastion in one step |
A custom environment is just another shell function:
create_lab() {
create_vpc
create_subnet
create_subnet
create_igw
attach_igw
create_route_table
create_route
launch_bastion
launch_webserver
}The toolkit does not distinguish between "manual" and "automated" workflows.
create_environment is simply a predefined composition of the same primitives.
Adjust the name pool in common.sh. Run it. Done. The orchestration is just another wrapper.
create_environment works the same way — 14 functions called in the correct sequence:
Subnets → Route Tables → IGW → Routes → DNS
The dependency order is the knowledge. The automation is a consequence.
VPC (10.0.0.0/16)
├── Public Subnet (10.0.1.0/24) → Bastion EC2 (public IP, SSH from your IP only)
├── Private Subnet (10.0.2.0/24) → WebServer EC2 (private IP, SSH via Bastion)
├── Internet Gateway
├── Route Tables (public: 0.0.0.0/0 → IGW | private: 0.0.0.0/0 → Bastion ENI)
└── Security Groups (chained: Bastion → WebServer → Database)
💡 The Bastion can be configured as a NAT instance, giving the private subnet outbound internet access without a managed NAT Gateway.
vpc.sh — VPC lifecycle
create_vpc · list_vpcs · select_vpc · delete_vpc · delete_all_vpcs
subnet.sh — Subnet lifecycle, auto CIDR assignment
create_subnet · list_subnets · select_subnet · delete_subnet · delete_all_subnets
dns_igw.sh — Internet Gateway + DNS settings
create_igw · attach_igw · detach_igw · enable_dns · disable_dns · detach_all_igws · delete_all_igws
route_tables.sh — Route tables, routes, associations
create_route_table · create_route · associate_route_table · disassociate_route_table · delete_all_routes · delete_all_route_tables · delete_all_associations
sg.sh — Security groups and ingress rules
create_security_group · authorize_ingress · revoke_ingress · list_rules · create_bastion_sg · create_webserver_sg · create_database_sg · delete_all_rules · delete_all_sgs
instance.sh — EC2, key pairs, NAT, SSH shortcuts
launch_bastion · launch_webserver · launch_instance · enable_nat_instance · ssh_bastion · ssh_webserver · refresh_bastion_access_in_sg · start_all_instances · stop_all_instances · delete_all_instances
infra.sh — Orchestration
create_environment · list_environment · auto_delete_environment
A complete cycle was run on 2026-06-06. Full terminal output and screenshots are in demo/.
📸 demo/screenshots/README.md is one scrollable file — function source first, then terminal output, for each operation. The three orchestration functions (create_environment, list_environment, auto_delete_environment) are shown as code screenshots so the structure of the tool is visible alongside what it produces.
The NAT proof is the most concrete part:
ping google.comfrom a private EC2 with no public IP — 100% loss before the Bastion bootstrap → 0% loss after. The route table, the source/destination check, and theiptables MASQUERADErule — all of it visible in the logs.
# Prerequisites: AWS CLI v2 configured, Bash 5+, ssh-agent running
git clone https://github.com/your-username/aws-infra-cli.git
cd aws-infra-cli
source scripts/infra.shFrom there, every function is available in your shell. Call them individually or run create_environment for the full stack.
📄 See docs/SETUP.md for prerequisites and configuration.
aws-infra-cli/
├── scripts/
│ ├── com_arg.sh # Central config: name pools, DB paths, color functions
│ ├── vpc.sh # VPC lifecycle
│ ├── subnet.sh # Subnet lifecycle, auto CIDR assignment
│ ├── dns_igw.sh # Internet Gateway + DNS settings
│ ├── route_tables.sh # Route tables, routes, subnet associations
│ ├── sg.sh # Security groups and ingress rules
│ ├── instance.sh # EC2, key pairs, NAT, SSH shortcuts
│ └── infra.sh # Orchestration: create / list / delete
├── docs/
│ ├── SETUP.md # Prerequisites and configuration
│ ├── ARCHITECTURE.md # Component design and NAT flow
│ ├── DESIGN.md # Key design decisions
│ ├── BUGS.md # Mistakes made and how each was fixed
│ └── STORY.md # How this got built
├── demo/
│ └── screenshots/ # Terminal output captures, organized by operation
├── database/ # Runtime state (.db files, excluded from git)
│ └── counters/
├── .gitignore
└── README.md
Each resource ID is stored locally in a pipe-delimited .db file on creation and removed on deletion. No querying AWS by tag — tag-based filters return terminated instances, which caused real bugs during development (documented in docs/BUGS.md).
| File | Schema |
|---|---|
database/vpcs.db |
vpc-id|name |
database/subnets.db |
subnet-id|name|type|vpc-id|cidr-octet |
database/security_groups.db |
sg-id|name|description|vpc-id |
database/ec2.db |
instance-id|name|subnet-id|sg-id|state |
Twelve .db files in total. State survives session resets. See docs/ARCHITECTURE.md for the full schema.
| Layer | Details |
|---|---|
| Language | Bash 5+ |
| Cloud | AWS CLI v2 |
| State | Flat pipe-delimited .db files |
| SSH | Agent forwarding, jump through Bastion |
| NAT | Bastion as NAT instance (source/dest check disabled, iptables MASQUERADE) |
- Full networking stack: VPC, subnets, IGW, route tables, security groups
- Compute: key pair management, AMI caching, EC2 launch and lifecycle
- NAT: Bastion as NAT instance with idempotent setup
- SSH: automatic SG IP refresh before every connection
- Cleanup: full teardown in correct dependency order
- CloudTrail integration (planned)
- S3 bucket management (planned)
- IAM least-privilege templates (planned)
MIT
