Skip to main content
Deploy Bifrost on AWS ECS using either Makefile automation or direct AWS CLI commands. This guide covers both Fargate and EC2 launch types, with options for managing configuration secrets.
This guide assumes you already have:
  • An ECS cluster
  • VPC with subnets
  • Security groups configured (must allow inbound traffic on port 8080 or your container port)
  • (Optional) Application Load Balancer with target group
Security Group Requirements:
  • For direct access (no load balancer): Allow inbound traffic on port 8080 (or CONTAINER_PORT) from your IP or 0.0.0.0/0
  • For load balancer: Allow inbound traffic from the load balancer’s security group

Deployment Methods

Choose your preferred deployment method:
  • Using Makefile
  • Using AWS CLI
  • Using CloudFormation

Quick Start with Makefile

The easiest way to deploy Bifrost to ECS is using the provided Makefile.
First-time deployment? If you don’t know your VPC ID or network configuration, run:
make list-ecs-network-resources
This will list all available VPCs, subnets, and security groups in your AWS region.
# First, create your config.json file with your Bifrost configuration
cat > /tmp/bifrost-config.json <<EOF
{
  "config_store": {
    "enabled": true,
    "type": "postgres",
    "config": {
      "host": "your-db-host",
      "port": "5432",
      "user": "your-db-user",
      "password": "your-db-password",
      "db_name": "bifrost",
      "ssl_mode": "disable"
    }
  },
  "logs_store": {
    "enabled": true,
    "type": "postgres",
    "config": {
      "host": "your-db-host",
      "port": "5432",
      "user": "your-db-user",
      "password": "your-db-password",
      "db_name": "bifrost",
      "ssl_mode": "disable"
    }
  }
}
EOF

# Deploy with VPC ID (recommended - auto-fetches all subnets)
make deploy-ecs \
  VPC_ID='vpc-xxx' \
  SECURITY_GROUP_IDS='sg-xxx' \
  CONFIG_JSON_FILE='/tmp/bifrost-config.json'

# Deploy with specific subnet IDs
make deploy-ecs \
  SUBNET_IDS='subnet-xxx,subnet-yyy' \
  SECURITY_GROUP_IDS='sg-xxx' \
  CONFIG_JSON_FILE='/tmp/bifrost-config.json'

# Deploy with EC2 launch type and SSM Parameter Store
make deploy-ecs \
  LAUNCH_TYPE=EC2 \
  SECRET_BACKEND=ssm \
  VPC_ID='vpc-xxx' \
  SECURITY_GROUP_IDS='sg-xxx' \
  CONFIG_JSON_FILE='/tmp/bifrost-config.json'

# Deploy with Application Load Balancer
make deploy-ecs \
  VPC_ID='vpc-xxx' \
  SECURITY_GROUP_IDS='sg-xxx' \
  TARGET_GROUP_ARN='arn:aws:elasticloadbalancing:...' \
  CONFIG_JSON_FILE='/tmp/bifrost-config.json'

# Deploy without configuration secret
make deploy-ecs \
  VPC_ID='vpc-xxx' \
  SECURITY_GROUP_IDS='sg-xxx'

Available Makefile Parameters

ParameterDefaultDescription
ECS_CLUSTER_NAMEbifrost-clusterName of the ECS cluster
ECS_SERVICE_NAMEbifrost-serviceName of the ECS service
ECS_TASK_FAMILYbifrost-taskTask definition family name
IMAGE_TAGlatestBifrost Docker image tag
LAUNCH_TYPEFARGATELaunch type: FARGATE or EC2
SECRET_BACKENDsecretsmanagerSecret storage: secretsmanager or ssm
AWS_REGIONus-east-1AWS region
VPC_ID(optional*)VPC ID (auto-fetches all subnets in VPC)
SUBNET_IDS(optional*)Comma-separated subnet IDs (if VPC_ID not provided)
SECURITY_GROUP_IDS(required)Comma-separated security group IDs
TARGET_GROUP_ARN(optional)ALB target group ARN
CONTAINER_PORT8080Container port
SECRET_NAMEbifrost/configSecret/parameter name
CONFIG_JSON_FILE(optional)Path to config.json file
SECRET_ARN(optional)Existing secret ARN (skip auto-lookup)
EXECUTION_ROLE_ARN(optional)ECS task execution role ARN
TASK_ROLE_ARN(optional)ECS task role ARN
Network Configuration (*):
You must provide either VPC_ID OR SUBNET_IDS:
  • VPC_ID (recommended): Automatically fetches all subnets in the VPC. Simpler and works across all availability zones.
  • SUBNET_IDS: Specify exact subnet IDs if you want fine-grained control over subnet placement.

Makefile Targets

  • list-ecs-network-resources: List available VPCs, subnets and security groups in your AWS region (helpful for first deployment)
  • deploy-ecs: Complete deployment (creates secret if CONFIG_JSON_FILE provided, registers task definition, creates service, waits for stabilization, and shows deployment status)
  • create-ecs-secret: Create/update configuration secret (requires CONFIG_JSON_FILE parameter)
  • register-ecs-task-definition: Register new task definition (with or without secret)
  • create-ecs-service: Create or update ECS service
  • update-ecs-service: Force new deployment
  • tail-ecs-logs: Continuously tail CloudWatch logs in real-time (Ctrl+C to exit)
  • ecs-status: Show current service status, running tasks, and recent logs
  • get-ecs-url: Get the public URL/IP to access the service (works with or without load balancer)
  • cleanup-ecs: Remove service and deregister task definitions
CONFIG_JSON_FILE Parameter: This is optional. If provided, the Makefile will create a secret in AWS Secrets Manager or SSM Parameter Store and mount it in the ECS task. If omitted, the task will be deployed without a secret, and you can use other configuration methods (environment variables, mounted volumes, etc.).How Configuration Secrets Work: When CONFIG_JSON_FILE is provided, the deployment:
  1. Stores your config.json in AWS Secrets Manager or SSM Parameter Store
  2. Injects the secret as an environment variable BIFROST_CONFIG into the container
  3. Uses a custom entrypoint that:
    • Silently writes the secret content to /app/data/config.json
    • Exits with error only if BIFROST_CONFIG is not set
    • Then starts Bifrost normally
  4. Bifrost reads the configuration from the file at startup
This approach ensures your configuration is securely stored and properly mounted as a file, which is required by Bifrost. The entrypoint does not log any config data to keep logs clean and secure.

IAM Permissions

Task Execution Role

The task execution role (ecsTaskExecutionRole) needs the following permissions:
The Makefile automatically creates the CloudWatch log group /ecs/bifrost-task, so the execution role only needs CreateLogStream and PutLogEvents permissions, not CreateLogGroup.
  • For Secrets Manager
  • For SSM Parameter Store
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/ecs/bifrost-task:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:bifrost/config*"
    }
  ]
}

Accessing Your Service

Without Load Balancer

When deployed without a load balancer, the ECS task gets a public IP address. You can find it using AWS CLI:
# Get the public IP address of your running task
aws ec2 describe-network-interfaces \
  --network-interface-ids $(aws ecs describe-tasks \
    --cluster bifrost-cluster \
    --tasks $(aws ecs list-tasks \
      --cluster bifrost-cluster \
      --service-name bifrost-service \
      --region us-east-1 \
      --query 'taskArns[0]' \
      --output text) \
    --region us-east-1 \
    --query 'tasks[0].attachments[0].details[?name==`networkInterfaceId`].value' \
    --output text) \
  --region us-east-1 \
  --query 'NetworkInterfaces[0].Association.PublicIp' \
  --output text
Important Notes:
  • The public IP changes every time the task is restarted
  • You must allow inbound traffic on port 8080 (or your CONTAINER_PORT) in your security group
  • For production, consider using an Application Load Balancer for a stable endpoint
Testing your deployment:
# Test health endpoint (replace YOUR_PUBLIC_IP with the IP from above)
curl http://YOUR_PUBLIC_IP:8080/health

# Expected response
{"status":"ok"}

With Load Balancer

If you deployed with TARGET_GROUP_ARN, your service is accessible via the load balancer’s DNS name:
# Get the load balancer DNS name (replace YOUR_TARGET_GROUP_ARN with your actual ARN)
aws elbv2 describe-load-balancers \
  --load-balancer-arns $(aws elbv2 describe-target-groups \
    --target-group-arns YOUR_TARGET_GROUP_ARN \
    --region us-east-1 \
    --query 'TargetGroups[0].LoadBalancerArns[0]' \
    --output text) \
  --region us-east-1 \
  --query 'LoadBalancers[0].DNSName' \
  --output text

# Test via load balancer (replace YOUR_ALB_DNS with the DNS from above)
curl http://YOUR_ALB_DNS/health
The load balancer provides:
  • ✅ Stable DNS endpoint
  • ✅ SSL/TLS termination (if configured)
  • ✅ Health checks with automatic failover
  • ✅ Multiple task load balancing

Monitoring and Logs

Tail Logs (Makefile)

The easiest way to monitor your deployment logs:
# Tail logs in real-time (press Ctrl+C to exit)
make tail-ecs-logs

# Check service status and recent logs
make ecs-status
The deploy-ecs command automatically waits for the deployment to stabilize and shows you:
  • Deployment status (running/desired count)
  • Task details (ARN, status, health)
  • Recent logs (last 20 events)
After deployment completes, use make tail-ecs-logs to continuously monitor your application.

View Logs (AWS CLI)

# Tail logs using AWS CLI v2 (recommended)
aws logs tail /ecs/bifrost-task --follow --region us-east-1

# Get log stream names
aws logs describe-log-streams \
  --log-group-name /ecs/bifrost-task \
  --order-by LastEventTime \
  --descending \
  --max-items 5 \
  --region us-east-1

# View logs from a specific stream
aws logs get-log-events \
  --log-group-name /ecs/bifrost-task \
  --log-stream-name bifrost/bifrost/TASK_ID \
  --region us-east-1

Check Service Status

# Describe service
aws ecs describe-services \
  --cluster bifrost-cluster \
  --services bifrost-service \
  --region us-east-1

# List tasks
aws ecs list-tasks \
  --cluster bifrost-cluster \
  --service-name bifrost-service \
  --region us-east-1

# Describe task
aws ecs describe-tasks \
  --cluster bifrost-cluster \
  --tasks TASK_ARN \
  --region us-east-1

Cleanup

To remove all ECS resources:
# Using Makefile
make cleanup-ecs

# Or manually
# Delete service
aws ecs update-service \
  --cluster bifrost-cluster \
  --service bifrost-service \
  --desired-count 0 \
  --region us-east-1

aws ecs delete-service \
  --cluster bifrost-cluster \
  --service bifrost-service \
  --region us-east-1

# Deregister task definitions
aws ecs list-task-definitions \
  --family-prefix bifrost-task \
  --region us-east-1 \
  --query 'taskDefinitionArns[]' \
  --output text | \
  xargs -n 1 aws ecs deregister-task-definition --task-definition --region us-east-1

# Delete secret (optional)
aws secretsmanager delete-secret \
  --secret-id bifrost/config \
  --force-delete-without-recovery \
  --region us-east-1

# Or delete SSM parameter (optional)
aws ssm delete-parameter \
  --name /bifrost/config \
  --region us-east-1