Skip to main content
Amazon Elastic Container Service runs the same OCI container images that you would push to Kubernetes or OpenShift, but with the AWS control plane managing the scheduler, load-balancing, and service discovery. For Aletyx Enterprise Build of Kogito and Drools workloads (Decision Control, Decision Control Tower, the Tower Sidecar, the Playground), ECS is a low-friction target when your organisation already operates in AWS — you skip the Kubernetes operational footprint and inherit IAM, Secrets Manager, CloudWatch, and ALB-integrated TLS termination. This guide covers the production-shaped path: a single-region service backed by an Application Load Balancer, an external RDS PostgreSQL, and Secrets Manager for sensitive values. Both launch types (Fargate and EC2) are covered side-by-side.

When to choose ECS

ECS is a strong fit when:
  • Your operations team already runs other workloads on AWS and you don’t have a Kubernetes platform in the same region.
  • You want the smallest possible operational footprint — Fargate eliminates the node-management burden entirely.
  • You need tight IAM and VPC integration without bridging EKS IRSA, AWS Load Balancer Controller, or external-secrets.io.
  • TLS termination at the load balancer is acceptable (ACM-issued certs on the ALB).
ECS is not a good fit when:
  • You need workload portability across clouds (use Kubernetes or OpenShift instead).
  • You require custom scheduler behaviour, CRDs, or operators (use EKS or OpenShift).
  • Your release pipeline is already Helm-/Kustomize-based and you don’t want to rewrite it for aws ecs update-service.

Fargate vs. EC2 launch type

Images run unprivileged with a read-only root filesystem and writable /tmp. Both launch types satisfy those requirements without modification.

Prerequisites

  • AWS account with permissions to create ECS clusters, IAM roles, ALBs, target groups, and (optionally) RDS instances.
  • VPC with at least two private subnets in distinct Availability Zones for the tasks, and two public subnets for the ALB. The default VPC works for evaluation; production deployments should use a dedicated VPC.
  • Route 53 hosted zone (or another DNS provider) for the public hostname that points to the ALB.
  • ACM certificate in the same region as the ALB, covering the public hostname.
  • Container registry credentials — your private container registry requires an API token; create a Secret in AWS Secrets Manager with the docker-config JSON (see below) and reference it from the task execution role.
  • RDS PostgreSQL instance in the same VPC (Single-AZ for evaluation; Multi-AZ for production). Encryption at rest enabled; auto-minor-version upgrade enabled.
  • AWS CLI v2 with credentials that can call ecs, iam, logs, secretsmanager, and elbv2.
The examples below assume placeholders that you’ll substitute:

Architecture at a glance

Amazon ECS deployment architecture

Step 1 — IAM roles

ECS uses two IAM roles per task: the task execution role (used by ECS to pull the image and write logs) and the task role (used by your application code to call AWS APIs).

Task execution role

Attach the AWS-managed policy AmazonECSTaskExecutionRolePolicy and a custom policy granting access to the registry credentials and any Secrets Manager / SSM parameters referenced in the task definition:

Task role

For Aletyx Enterprise Build of Kogito and Drools workloads that do not integrate with AWS services beyond Secrets Manager (the common case), the task role can stay empty — the application reads its secrets via the task definition’s secrets block, which is evaluated by the agent under the execution role. Add permissions to the task role only when the application code itself calls AWS APIs (uncommon).

Step 2 — Registry credentials

Your private container registry requires an API token. Create a Secrets Manager secret in the docker-config JSON format expected by ECS:
Reference it from the task definition via repositoryCredentials.credentialsParameter. ECS rotates the in-flight pull on the next task launch, so token rotation is a no-downtime operation as long as the new value is in place before the next deployment.

Step 3 — Application secrets

Create one Secrets Manager secret per sensitive value (database password, OIDC client secret, etc.). Per-secret entries keep the IAM scope small and let you rotate one value at a time:
For non-sensitive configuration (URLs, log levels, feature flags), use SSM Parameter Store String parameters or hard-code the values in the task definition environment block — both are equivalent at runtime.

Step 4 — Task definition

The task definition below assumes a Decision Control workload. The same shape applies to Decision Control Tower and the Tower Sidecar — swap the image, port, and env block accordingly.
Key choices in the above:
  • networkMode: awsvpc — required for Fargate; recommended for EC2. Each task gets its own ENI, which simplifies security groups and gives the task an IP that the ALB can target directly (ip target type).
  • requiresCompatibilities: ["FARGATE"] — for EC2 launch type, swap to ["EC2"] and remove cpu / memory from the task level (move them to the container definition’s cpu / memoryReservation).
  • readonlyRootFilesystem: true with a tmpfs mount at /tmp — matches the hardening posture documented in the Kubernetes deployment guide.
  • user: "1001:1001" — runs as a non-root UID baked into the image.
  • healthCheck — both the container health check (here) and the ALB target group health check (next step) are valuable; the container check restarts the task locally, the ALB check reroutes traffic.

EC2 launch type variant

For EC2, replace the launch type and provide instance-side resource hints:
Everything else (env, secrets, logging, health check) is identical.

Step 5 — ALB + target group

The Application Load Balancer terminates TLS, routes by host header, and integrates with the ECS service via a target group that uses the ip target type (each task ENI is registered directly).
A short health-check interval (30s / 2 healthy) is recommended so a failed deployment is caught quickly. The path /actuator/health is the same endpoint used by the container health check and is unauthenticated by design.

Step 6 — ECS service

The deployment controller ECS runs a rolling update — new tasks are started, the ALB waits for them to be healthy, then old tasks drain. For blue-green deployments, switch to CODE_DEPLOY and define a CodeDeploy application + deployment group.

Auto Scaling

Decision Control is mostly request-bound, so target tracking against requests-per-target or CPU works well:

Step 7 — Security groups

Two security groups are typically enough:
  • ALB SG: ingress 443/tcp from 0.0.0.0/0 (public hostname).
  • Task SG: ingress 8080/tcp from the ALB SG only; egress to RDS port 5432/tcp and HTTPS 443/tcp (for the OIDC provider, image pulls, and Secrets Manager).
The RDS security group should permit 5432/tcp from the Task SG only. Never expose RDS publicly.

OIDC redirect URI

The OIDC provider’s registered redirect URI must exactly match ${APP_HOST}/auth/callback. With the ALB terminating TLS, APP_HOST=https://<your-public-hostname>. ECS sets X-Forwarded-Proto automatically; Aletyx Enterprise Build of Kogito and Drools respects it when FORWARD_HEADERS_STRATEGY=framework is set (the default in 1.6.0+). Common pitfall: the registered redirect URI in Entra / Keycloak still pointing at the old ALB DNS name from a previous test environment. Update the registration when promoting to a new ALB / hostname.

Logging and observability

The awslogs driver in the task definition streams stdout/stderr to a CloudWatch Logs group (/<your-namespace-prefix>/<your-app-name> in the example). Set a retention policy on the log group to control cost:
For metrics, both ECS Service Metrics (CPU, memory, task count) and the application’s own /actuator/prometheus endpoint are available. Wire the latter via a CloudWatch agent sidecar or an OTel collector if you already operate one — Aletyx Enterprise Build of Kogito and Drools workloads don’t ship a metrics-export sidecar by default.

Database connectivity

RDS in the same VPC is the production-recommended path. The task accesses the RDS endpoint via the private route. Use iam database authentication if your operating model already standardises on IAM credentials; otherwise the password-in-Secrets-Manager pattern shown above is the path of least resistance. For evaluation only, RDS Single-AZ + Burstable instance is sufficient. Production should run Multi-AZ + Provisioned IOPS or gp3 storage.

Operational tasks

Deploying a new image

The service rolls forward task-by-task; the ALB drains old tasks once new ones report healthy.

Rotating a secret

Update the secret value in Secrets Manager. Existing tasks continue to hold the old value in memory — they pick up the new one on the next start. Force a rotation by aws ecs update-service --force-new-deployment, which restarts tasks without changing the task definition.

Scaling manually

Troubleshooting

Task stops immediately with ResourceInitializationError

The execution role can’t fetch the registry credentials or one of the Secrets Manager values. Check CloudTrail for GetSecretValue denials and confirm the role has access to every secret referenced in the task definition.

Task stops with Essential container exited after a few seconds

Usually a startup failure inside the app. The first place to look is CloudWatch Logs at /<your-namespace-prefix>/<your-app-name>/<stream>kubectl logs --previous equivalent. The two common causes are a stale schema (HIBERNATE_DDL_AUTO=validate against a DB without the latest migrations) and an Unable to acquire JDBC Connection from a misconfigured SPRING_DATASOURCE_URL or a Security Group that denies port 5432.

ALB target stuck unhealthy

If the task is Running but the ALB target is unhealthy:
  • Confirm the target group port matches the container port (8080 by default).
  • Confirm the security group on the task allows ingress from the ALB SG on port 8080.
  • Curl the health endpoint from a bastion or another task in the same SG: curl -v http://<task-private-ip>:8080/actuator/health.
A target that takes longer than health-check-grace-period-seconds to come healthy will be terminated and replaced — increase the grace period to 90–120 seconds for cold JVM starts.

OIDC redirect_uri_mismatch

The OIDC provider’s registered redirect URI does not exactly equal ${APP_HOST}/auth/callback. Common causes: missing /auth/callback path, http:// vs. https://, or a stale ALB DNS name still registered with the provider.

Costs are higher than expected

If you launched with Fargate and the workload is steady-state high-utilisation, EC2 launch type is usually 30–50% cheaper at the per-vCPU-hour level. Capacity Providers let you mix Fargate (baseline) with Fargate Spot (burst) for additional savings on non-critical environments.