In previous posts we explored how to create an EC2 instance using Terraform. However, to make the application production-ready, a few more important steps are still needed. We need to:
- Create an Elastic IP and attach to the instance
- Register a domain (Route 53 preferrably) and point to the Elastic IP
- Create Route 53 records, such as
yourdomain.comwww.yourdomain.com - Launch the instance with a predefined script for installations (optional)
- Configure HTTPS
- Update the
nginxcontainer anddefault.confto handle HTTPS traffic
Elastic IP
Elastic IP can be thought of as a static IP and it is better to point your domain to the elastic IP rather than to the public IP because when the instance restarts, public IP can change. Using an Elastic IP ensures that your application remains accessible at the same address even if the EC2 instance is stopped and started again.
In Terraform you can create and attach an elastic IP to your instance like this:
resource "aws_eip" "app_eip" {
domain = "vpc"
tags = {
Name = "app-eip"
}
}
resource "aws_eip_association" "app_eip_assoc" {
instance_id = aws_instance.web.id
allocation_id = aws_eip.app_eip.id
}
Now when the Elastic IP is created and attached to the instance, it is time to get a domain. Assuming the domain is purchased using AWS Route 53, you can reference it in Terraform and point it to your Elastic IP. Managing DNS records through Terraform also allows your infrastructure and configuration to remain fully reproducible.
Domain Handling
variable "domain_name" {
description = "Root domain name"
type = string
}
data "aws_route53_zone" "zone" {
name = var.domain_name
private_zone = false
}
resource "aws_route53_record" "root" {
zone_id = data.aws_route53_zone.zone.zone_id
name = var.domain_name
type = "A"
ttl = 300
records = [aws_eip.app_eip.public_ip]
}
resource "aws_route53_record" "www_root" {
zone_id = data.aws_route53_zone.zone.zone_id
name = "www.${var.domain_name}"
type = "A"
ttl = 300
records = [aws_eip.app_eip.public_ip]
}
var.domain_name is simply a variable referenced in the terraform.tfvars, for example domain_name = "yourdomain.com"
terraform.tfvars.-
sudo yum install certbot python3-certbot-nginx -
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
After the certificates are generated, verify that /etc/letsencrypt/live/ exists on the host and contains the expected certificate files. These certificates will later be mounted into the nginx container.
Now we need to modify nginx's default.conf and later the container to properly handle HTTPS traffic.
In default.conf we define two server blocks. Port 80 now redirects traffic to port 443, ensuring that all requests use HTTPS. Port 443 is configured to handle secure traffic using the certificates generated by Certbot.
Previously created ssl_certificate files are referenced.
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
client_max_body_size 10M;
server_name yourdomain.com www.yourdomain.com;
# SSL certificate files
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location /static/ {
alias /app/static/;
expires 1d;
add_header Cache-Control "public";
}
location / {
limit_req zone=mylimit burst=10 nodelay;
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
This setup is similar to the one described in Gunicorn and Nginx Setup for Serving the Web App, with the key difference being the addition of redirecting, HTTPS support and certificate handling.
Updating the Nginx Container
Now it time to modify the nginx container. Since the SSL certificates are generated on the host, they must be mounted into the container so Nginx can access them.
In Gunicorn and Nginx Setup for Serving the Web App post's setup, the container only exposed port 80. We now expose port 443 as well and mount the /etc/letsencrypt directory as read-only. The updated Docker Compose configuration looks like this:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
- "443:443"
depends_on:
- web
volumes:
- /etc/letsencrypt:/etc/letsencrypt:ro
- ./src/nginx/conf.d:/etc/nginx/conf.d
- ./nginx/logs:/var/log/nginx
- ./src/staticfiles:/app/static
restart: always
networks:
- myproject-net
Assuming your web container is running on port 8000 and the nginx container is also successfully running, opening yourdomain.com or www.yourdomain.com in the browser should now securely serve the web app over HTTPS.