Skip to main content

Overview

The backend API runs as a Docker container on AWS ECS Fargate behind an Application Load Balancer. This section covers building the Docker image, pushing to ECR, and deploying to ECS.

Deployment Architecture

Build Docker Image

The backend uses a Python 3.11 slim base image with FastAPI and crawling dependencies.

Review Dockerfile

The backend/Dockerfile contains:
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

EXPOSE 8000

# Run FastAPI server
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
The container exposes port 8000 for the FastAPI application.

Build the Image

Navigate to backend directory and build:
cd backend
docker build -t llmstxt-api:latest .
Expected output:
[+] Building 45.2s (11/11) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 350B
 => [internal] load .dockerignore
 => [1/6] FROM docker.io/library/python:3.11-slim
 => [2/6] WORKDIR /app
 => [3/6] RUN apt-get update && apt-get install -y curl
 => [4/6] COPY requirements.txt .
 => [5/6] RUN pip install --no-cache-dir -r requirements.txt
 => [6/6] COPY . .
 => exporting to image
 => => writing image sha256:abc123...
 => => naming to docker.io/library/llmstxt-api:latest
Docker image built successfully! Image size should be around 500-700MB.

Test Image Locally (Optional)

Test the built image before pushing to ECR:
# Create .env file with test credentials
cat > .env <<EOF
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-key
R2_ENDPOINT=https://your-endpoint.r2.cloudflarestorage.com
R2_ACCESS_KEY=your-access-key
R2_SECRET_KEY=your-secret-key
R2_BUCKET=llmstxt
R2_PUBLIC_DOMAIN=https://pub-xxxxx.r2.dev
CORS_ORIGINS=*
API_KEY=test-key
CRON_SECRET=test-secret
EOF

# Run container
docker run -p 8000:8000 --env-file .env llmstxt-api:latest
Test the health endpoint:
curl http://localhost:8000/health
# Expected: {"status":"ok"}
Stop the container with Ctrl+C.

Push Image to Amazon ECR

Get ECR Repository URL

Retrieve the ECR repository URL from Terraform:
cd ../terraform
terraform output ecr_repository_url
Example output:
123456789012.dkr.ecr.us-east-1.amazonaws.com/llmstxt-api
Save this as an environment variable:
export ECR_REPO=$(terraform output -raw ecr_repository_url)
echo $ECR_REPO

Authenticate Docker with ECR

Log in to ECR using AWS CLI:
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  $ECR_REPO
Expected output:
Login Succeeded
ECR authentication tokens expire after 12 hours. Re-run this command if you get authentication errors.

Tag Image for ECR

Tag the local image with the ECR repository URL:
cd ../backend
docker tag llmstxt-api:latest $ECR_REPO:latest
Verify the tag:
docker images | grep llmstxt-api
You should see both tags:
llmstxt-api                                    latest    abc123...  5 minutes ago  650MB
123456789012.dkr.ecr.us-east-1.amazonaws.com/llmstxt-api   latest    abc123...  5 minutes ago  650MB

Push Image to ECR

Push the tagged image to ECR:
docker push $ECR_REPO:latest
Expected output:
The push refers to repository [123456789012.dkr.ecr.us-east-1.amazonaws.com/llmstxt-api]
5f70bf18a086: Pushed
6e62e7c2e5a5: Pushed
3abc4def5678: Pushed
...
latest: digest: sha256:1234567890abcdef... size: 2345
This takes 2-5 minutes depending on your internet connection.
Image successfully pushed to ECR!

Deploy to ECS

Force New Deployment

Trigger ECS to pull the new image and deploy:
aws ecs update-service \
  --cluster llmstxt-cluster \
  --service llmstxt-api-service \
  --force-new-deployment \
  --region us-east-1
Expected output:
{
    "service": {
        "serviceName": "llmstxt-api-service",
        "clusterArn": "arn:aws:ecs:us-east-1:...:cluster/llmstxt-cluster",
        "deployments": [
            {
                "status": "PRIMARY",
                "desiredCount": 1,
                "runningCount": 0
            }
        ]
    }
}

Monitor Deployment Progress

Watch the deployment status:
watch -n 5 'aws ecs describe-services \
  --cluster llmstxt-cluster \
  --services llmstxt-api-service \
  --query "services[0].deployments" \
  --region us-east-1'
Press Ctrl+C to stop watching. Deployment typically takes 3-5 minutes.
Look for:
  • Running count: Should increase from 0 to 1
  • Desired count: Should be 1
  • Status: Should be “PRIMARY”

Check Task Status

Verify the ECS task is running:
aws ecs list-tasks \
  --cluster llmstxt-cluster \
  --service-name llmstxt-api-service \
  --region us-east-1
Expected output:
{
    "taskArns": [
        "arn:aws:ecs:us-east-1:...:task/llmstxt-cluster/abc123def456"
    ]
}
If you see a task ARN, the container is running!

View Task Details

Get detailed task information:
# Get task ARN
TASK_ARN=$(aws ecs list-tasks \
  --cluster llmstxt-cluster \
  --service-name llmstxt-api-service \
  --query 'taskArns[0]' \
  --output text \
  --region us-east-1)

# Describe task
aws ecs describe-tasks \
  --cluster llmstxt-cluster \
  --tasks $TASK_ARN \
  --region us-east-1
Check lastStatus: should be RUNNING.

Verify Deployment

Get Load Balancer URL

cd ../terraform
terraform output alb_dns_name
Example output:
llmstxt-alb-1234567890.us-east-1.elb.amazonaws.com

Test Health Endpoint

Wait 30-60 seconds for the ALB health check to pass, then test:
curl http://$(terraform output -raw alb_dns_name)/health
Expected response:
{"status":"ok"}
Backend API is live and healthy!

Test WebSocket Endpoint

Install websocat for WebSocket testing:
# macOS
brew install websocat

# Linux
wget https://github.com/vi/websocat/releases/latest/download/websocat_linux64
chmod +x websocat_linux64
sudo mv websocat_linux64 /usr/local/bin/websocat
Test crawl endpoint:
ALB_URL=$(cd terraform && terraform output -raw alb_dns_name)
API_KEY="your-api-key-from-terraform-tfvars"

echo '{"url":"https://example.com","maxPages":5,"descLength":200}' | \
  websocat "ws://${ALB_URL}/ws/crawl?api_key=${API_KEY}"
You should see streaming crawl progress messages.

View Container Logs

Via AWS CLI

View recent application logs:
aws logs tail /ecs/llmstxt-api \
  --follow \
  --region us-east-1

Via AWS Console

  1. Go to CloudWatch Console
  2. Navigate to LogsLog groups
  3. Open /ecs/llmstxt-api
  4. View log streams (one per task/container)

Check for Errors

Search logs for error messages:
aws logs filter-log-events \
  --log-group-name /ecs/llmstxt-api \
  --filter-pattern "ERROR" \
  --region us-east-1

Updating the Application

To deploy code changes:
1

Make Code Changes

Edit backend code in backend/ directory.
2

Rebuild Docker Image

cd backend
docker build -t llmstxt-api:latest .
3

Tag and Push to ECR

docker tag llmstxt-api:latest $ECR_REPO:latest
docker push $ECR_REPO:latest
4

Force ECS Deployment

aws ecs update-service \
  --cluster llmstxt-cluster \
  --service llmstxt-api-service \
  --force-new-deployment \
  --region us-east-1
5

Monitor Deployment

Watch deployment progress and verify health endpoint.
ECS performs a rolling deployment: new task starts before old task stops (zero downtime).

Troubleshooting

Check task stopped reason:
aws ecs describe-tasks \
  --cluster llmstxt-cluster \
  --tasks $TASK_ARN \
  --query 'tasks[0].stoppedReason'
Common issues:
  • Image pull error: Verify ECR authentication
  • Task execution role: Check IAM permissions
  • Environment variables: Verify Terraform configuration
Check ALB target health:
aws elbv2 describe-target-health \
  --target-group-arn $(cd terraform && terraform output -raw target_group_arn)
If unhealthy:
  1. Verify container is listening on port 8000
  2. Check security group allows ALB → ECS traffic
  3. Review container logs for startup errors
Verify security group:
aws ec2 describe-security-groups \
  --filters "Name=tag:Name,Values=llmstxt-alb-sg" \
  --query 'SecurityGroups[0].IpPermissions'
Ensure port 80 and 443 are open to 0.0.0.0/0.
Increase task memory:Edit terraform/ecs.tf:
resource "aws_ecs_task_definition" "llmstxt_api" {
  # ...
  memory = "2048"  # Increase from 1024 to 2048 (2GB)
}
Apply changes:
cd terraform
terraform apply

Scaling Configuration

Manual Scaling

Increase the number of running tasks:
aws ecs update-service \
  --cluster llmstxt-cluster \
  --service llmstxt-api-service \
  --desired-count 2 \
  --region us-east-1

Auto Scaling (Optional)

Add to terraform/ecs.tf:
resource "aws_appautoscaling_target" "ecs_target" {
  max_capacity       = 4
  min_capacity       = 1
  resource_id        = "service/${aws_ecs_cluster.llmstxt.name}/${aws_ecs_service.llmstxt_api.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

resource "aws_appautoscaling_policy" "ecs_cpu_scaling" {
  name               = "llmstxt-cpu-scaling"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.ecs_target.resource_id
  scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace

  target_tracking_scaling_policy_configuration {
    target_value       = 70.0
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
  }
}
Apply with terraform apply.

Next Steps

Frontend Deployment

Deploy the Next.js frontend to Vercel

Build docs developers (and LLMs) love