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 one subnet? Run create_subnet.
Need to revoke one SG rule? Run revoke_ingress.
Need to SSH into the WebServer? Run ssh_webserver — it detects your current IP, updates the security group rule, and connects through the Bastion in one step.
Every function is independently usable and composable.
For example, 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 before route tables. Route tables before associations. IGW before routes. DNS after the VPC is stable. 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.
| Module | What it covers |
|---|---|
vpc.sh |
create_vpc, list_vpcs, select_vpc, delete_vpc, delete_all_vpcs |
subnet.sh |
create_subnet, list_subnets, select_subnet, delete_subnet, delete_all_subnets |
dns_igw.sh |
create_igw, attach_igw, detach_igw, enable_dns, disable_dns, detach_all_igws, delete_all_igws |
route_tables.sh |
create_route_table, create_route, associate_route_table, disassociate_route_table, delete_all_routes, delete_all_route_tables, delete_all_associations |
sg.sh |
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 |
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 |
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.com from a private EC2 with no public IP — 100% loss before the Bastion bootstrap, 0% loss after. The route table, the source/destination check, the iptables MASQUERADE rule — 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).
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