Design VPC for Python Services
Design a secure and efficient VPC architecture for your Python services on AWS. Learn core concepts, step-by-step setup, and best practices.
Focus: design vpc architecture for python services
Imagine your Python microservices are running on AWS, but they're exposed to the world because you never bothered to design a proper Virtual Private Cloud (VPC). A single misconfigured security group or a public subnet hosting your database can lead to a data breach that costs your company millions. This lesson is your blueprint for designing a VPC architecture that keeps your Python services secure, isolated, and highly available — the way real DevOps engineers do it.
The problem this lesson solves
When you launch a Python service on EC2, ECS, or Lambda, the underlying network matters more than your code. Without a well-designed VPC, you face:
- Security risks: Your database or internal APIs are accessible from the internet because they're in a public subnet.
- Latency issues: Your services can't talk to each other efficiently because they're spread across disjointed networks.
- Operational chaos: You can't scale or debug because there's no clear structure to your subnets, route tables, or security groups.
A VPC (Virtual Private Cloud) is your own isolated section of AWS. Designing it correctly is the difference between a hobby project and a production-grade system. By the end of this lesson, you'll be able to design a VPC that supports secure, scalable, and maintainable Python services.
Core concept / mental model
Think of a VPC as a gated neighborhood for your AWS resources. Inside this neighborhood, you have:
- Subnets — individual streets. Some streets are public (with direct access to the main road), others are private (dead ends with no external entry).
- Route tables — the traffic signs that tell packets which way to go.
- Internet Gateway (IGW) — the main gate to the outside world.
- NAT Gateway — a secure back door that lets private subnet resources reach the internet (e.g., to download packages) but never allows inbound traffic.
- Security Groups — the front-door guards for each house (your EC2 instances or RDS databases).
Your Python services typically live in private subnets, where they can communicate with each other and access the internet outbound via a NAT Gateway. Only the load balancer or a bastion host sits in a public subnet, acting as the entry point.
Here's a simple mental picture:
Internet ---> IGW ---> Public Subnet (ALB) ---> Private Subnet (Python services) ---> Private Subnet (RDS)
^ |
+---------- NAT Gateway <--------------+
The key idea: layers of isolation. Each layer reduces the attack surface and gives you control over what's exposed.
How it works step by step
Designing a VPC for Python services follows a repeatable pattern. Let's break it down:
-
Plan your CIDR range: Choose a private IP range, e.g.,
10.0.0.0/16. This gives you ~65,000 addresses — plenty for most services. -
Create subnets across Availability Zones (AZs): For high availability, create at least two public and two private subnets in different AZs. Each subnet gets a slice of your CIDR, like
10.0.1.0/24and10.0.2.0/24. -
Attach an Internet Gateway (IGW): This is the doorway to the internet. Without it, nothing in the VPC can reach the outside world (or be reached from outside).
-
Configure route tables: - Public route table: route
0.0.0.0/0to the IGW. - Private route table: route0.0.0.0/0to a NAT Gateway (for outbound-only internet access). -
Set up a NAT Gateway: Place it in a public subnet with an Elastic IP. This allows Python services in private subnets to fetch pip packages or call external APIs without exposing themselves.
-
Define security groups and NACLs: Security groups are stateful firewalls attached to resources. NACLs are stateless rules applied at the subnet level. Use security groups primarily for fine-grained control (e.g., "allow SSH only from my office IP").
-
Place your resources: Put your application load balancer in public subnets, your Python EC2/ECS tasks in private subnets, and your RDS database in a private subnet with a security group that only allows traffic from your app tier.
Hands-on walkthrough
Let's build a minimal VPC architecture using the AWS CLI. We'll use Python to generate the commands, making them repeatable and scriptable.
First, set up your VPC and subnets:
import subprocess
import json
vpc_name = "python-app-vpc"
cidr = "10.0.0.0/16"
# Create the VPC
vpc_response = subprocess.run(["aws", "ec2", "create-vpc", "--cidr-block", cidr, "--output", "json"], capture_output=True, text=True)
vpc_id = json.loads(vpc_response.stdout)["Vpc"]["VpcId"]
print(f"VPC created: {vpc_id}")
# Create subnets (public and private in two AZs)
azs = ["us-east-1a", "us-east-1b"]
subnet_cidrs = {"public": ["10.0.1.0/24", "10.0.2.0/24"], "private": ["10.0.3.0/24", "10.0.4.0/24"]}
for az, cidr in zip(azs, subnet_cidrs["public"]):
subprocess.run(["aws", "ec2", "create-subnet", "--vpc-id", vpc_id, "--cidr-block", cidr, "--availability-zone", az, "--output", "json"], stdout=subprocess.DEVNULL)
print(f"Public subnet in {az}: {cidr}")
for az, cidr in zip(azs, subnet_cidrs["private"]):
subprocess.run(["aws", "ec2", "create-subnet", "--vpc-id", vpc_id, "--cidr-block", cidr, "--availability-zone", az, "--output", "json"], stdout=subprocess.DEVNULL)
print(f"Private subnet in {az}: {cidr}")
Expected output:
VPC created: vpc-0abcd1234efgh5678
Public subnet in us-east-1a: 10.0.1.0/24
Public subnet in us-east-1b: 10.0.2.0/24
Private subnet in us-east-1a: 10.0.3.0/24
Private subnet in us-east-1b: 10.0.4.0/24
Next, create the Internet Gateway, attach it, and set up the public route table:
import subprocess
import json
# Create IGW and attach to VPC
igw_response = subprocess.run(["aws", "ec2", "create-internet-gateway", "--output", "json"], capture_output=True, text=True)
igw_id = json.loads(igw_response.stdout)["InternetGateway"]["InternetGatewayId"]
subprocess.run(["aws", "ec2", "attach-internet-gateway", "--internet-gateway-id", igw_id, "--vpc-id", vpc_id], stdout=subprocess.DEVNULL)
print(f"IGW created and attached: {igw_id}")
# Create public route table and add route to IGW
rt_response = subprocess.run(["aws", "ec2", "create-route-table", "--vpc-id", vpc_id, "--output", "json"], capture_output=True, text=True)
public_rt_id = json.loads(rt_response.stdout)["RouteTable"]["RouteTableId"]
subprocess.run(["aws", "ec2", "create-route", "--route-table-id", public_rt_id, "--destination-cidr-block", "0.0.0.0/0", "--gateway-id", igw_id], stdout=subprocess.DEVNULL)
print(f"Public route table ready: {public_rt_id}")
# Now you would associate the public subnets with this route table
Expected output:
IGW created and attached: igw-0abc123def456ghij
Public route table ready: rtb-0abc123def456ghij
Now, set up a NAT Gateway for private subnets:
# Allocate an Elastic IP
aws ec2 allocate-address --domain vpc
# Create a NAT Gateway in a public subnet (replace subnet-id and allocation-id)
aws ec2 create-nat-gateway --subnet-id subnet-0public1 --allocation-id eipalloc-0abc123
# Then create a private route table with a route to the NAT Gateway
aws ec2 create-route-table --vpc-id vpc-0abcd1234efgh5678
aaws ec2 create-route --route-table-id rtb-0private --destination-cidr-block 0.0.0.0/0 --nat-gateway-id nat-0abc123
Once your network is up, launch a Python service on an EC2 instance in a private subnet with a security group that only allows traffic from your load balancer. Here's how your security group rules might look:
# Example security group rules (via AWS console or CLI)
# ALB SG: allow inbound 80/443 from 0.0.0.0/0
# App SG: allow inbound 8000 from ALB SG ID
# DB SG: allow inbound 5432 from App SG ID
This layered approach ensures your Python code runs securely and efficiently.
Compare options / when to choose what
When designing a VPC for Python services, you have several choices. Here's a quick comparison:
| Option | When to use | Trade-offs |
|---|---|---|
| Public subnet for everything | Dev/test only | Simple, but insecure for production |
| Private subnets + NAT | Production services needing outbound access | Extra cost (~$32/mo per NAT), but secure |
| VPC endpoints for S3/DynamoDB | Avoid NAT for AWS services | Lower cost, but only for specific AWS services |
| Single AZ | Cheap prototypes | No high availability |
| Multi-AZ (2 or more) | Production | Higher cost, but resilient |
Decision rule: Use private subnets for anything that holds data or processes requests. Only expose the load balancer and bastion host publicly.
Troubleshooting & edge cases
- No internet from EC2 instance: Check that your instance is in a public subnet with a route to the IGW, or in a private subnet with a route to the NAT. Verify security group outbound rules allow traffic.
- Cannot SSH to private instance: You need a bastion host in the public subnet, then use SSH agent forwarding to hop into the private instance.
- Two instances in different subnets can't talk: Ensure the security groups allow traffic between them. Check NACL rules (they are stateless, so the response traffic must be explicitly allowed).
- NAT Gateway costs: If you're on a tight budget, consider using a NAT instance (a standalone EC2) or VPC endpoints for AWS services.
- CIDR overlap: When peering VPCs, ensure your CIDR ranges do not overlap with the other VPC.
What you learned & what's next
You now understand how to design a VPC architecture for Python services: you can plan subnets, configure route tables, set up security groups, and decide when to use public vs private subnets. You applied this in a hands-on walkthrough using the AWS CLI and Python. This foundation is critical for the next lesson, where you'll deploy an actual Python web app inside this VPC using Elastic Beanstalk or ECS, connecting it to an RDS database — all without exposing anything unnecessarily.
Remember: a well-designed VPC is not optional for production Python services. It's your first line of defense and the backbone of your infrastructure.
Practice recap
As a next step, use the AWS CLI or a Python script to create a VPC with two public and two private subnets, attach an IGW, and configure route tables. Verify your setup by launching a simple EC2 instance in a private subnet and confirming it can reach the internet via a NAT Gateway. This hands-on exercise solidifies the concepts you just learned.
Common mistakes
- Placing RDS or Python backend EC2 instances in a public subnet, exposing them to the internet.
- Forgetting to attach an Internet Gateway to the VPC, then wondering why a public instance can't reach the internet.
- Using a single availability zone for all subnets, creating a single point of failure.
- Putting a NAT Gateway in a private subnet — it won't have internet access itself.
- Not associating subnets with the correct route table, leading to routing errors.
Variations
- Use AWS CloudFormation or Terraform to define the VPC as code, making it reproducible and version-controlled.
- Use a single NAT instance instead of a NAT Gateway to reduce cost, at the expense of high availability.
- Implement a fully serverless architecture using Lambda inside a VPC, which requires VPC endpoints for services like DynamoDB.
Real-world use cases
- Deploying a Python REST API on ECS behind an Application Load Balancer in a multi-AZ VPC.
- Running a Python ETL pipeline on EC2 instances that access the internet for external data but stay private.
- Hosting a Django app with a PostgreSQL RDS database, where the DB is in a private subnet and only the web tier can reach it.
Key takeaways
- A VPC isolates your AWS resources; subnets, route tables, IGW, and NAT are the core building blocks.
- Private subnets are for backend services; public subnets only for load balancers and bastions.
- Multi-AZ design is essential for high availability of production Python services.
- Security groups are the primary firewall; NACLs are a secondary layer — use both wisely.
- NAT Gateway enables outbound internet access for private resources, but adds cost.
- Design your CIDR plan carefully to avoid conflicts when peering VPCs.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.