Vector-Database: Qdrant-cluster on ECS-Fargate
Source: Dev.to
When do you need this?
- When SaaS is not viable due to compliance reasons or network‑performance constraints.
- When the vector‑DB must live in the same AWS account as the rest of the solution.
- When you don’t want (or can’t use) Kubernetes.
Questions? See the article #2 for a deep dive into why certain design decisions have to be that way.
This article (#1) focuses on CDK snippets to get you started quickly (for those already familiar with CDK).
All of this (CDK, ECS & clusters) feels overwhelming?
Seek professional services from Qdrant.tech or from me / my current employer (an AWS partner).
Short Summary
- Run a 4‑ or 6‑node Qdrant cluster on ECS, spread across 2‑3 AZs.
- Keep it simple: let AWS handle availability, uptime, and AZ‑balancing.
- Use a single endpoint for the whole cluster, regardless of node failures/replacements.
- No EBS – rely on EFS for native snapshots and data protection.
- Secure the Qdrant Dashboard via ALB + Qdrant‑native API key.

How do I … ?
- Article #2 – Full details on the critical design & key requirements that shaped the final implementation.
- Article #3 – Snapshots, data restore, and backup strategy.
- GitLab repo – Contains the complete CDK construct.
- Assumption – You’ll build a custom Qdrant container image (Dockerfile) from Qdrant’s GitHub repo. See article #4 for a sensible Dockerfile.
Below are the key “lego‑blocks” for CDK experts. (See article #2 again for the why behind each choice.)
Key Variables
// Paths / names
bashScriptFromArticle4 = ".../local-path-inside-docker__to_bash-script-from-article-4...";
ecsClusterName = `${baseName}-${cpuArchStr}`;
fargateContainerName = `${baseName}-${cpuArchStr}`;
fargateServiceName = `ECSSvc-${fargateContainerName}`;
taskDefContainerName = `cont-${fargateContainerName}`;
domainName = `${ecsClusterName}.local`;
qdrantFQDN = `qdrant-node.${domainName}`;
// Secrets
nativeApiKeySecret = new aws_secretsmanager.Secret(...);
// excludeCharacters = "~`@#$%^&*+={}[]()|\\:;'\"”<>,/? ";
Fargate Task Definition
new aws_ecs.FargateTaskDefinition(this, "QdrantTaskDef", {
family: fargateContainerName, // Equivalent to task name
cpu: ECS_FARGATE_CPU_SIZING + 512,
memoryLimitMiB: ECS_FARGATE_MEMORY_SIZING_GB * 1024 + 1024,
enableFaultInjection: basicProps.tier === "dev",
ephemeralStorageGiB: 22, // Must be 21‑200 GiB
runtimePlatform: {
cpuArchitecture: ecsCpuArchitecture,
operatingSystemFamily: aws_ecs.OperatingSystemFamily.LINUX,
},
volumes: [
{
name: fargateContainerName, // Must be referenced in the Service
efsVolumeConfiguration: {
fileSystemId: efsFileSystem.fileSystemId,
rootDirectory: "/",
transitEncryption: "ENABLED",
authorizationConfig: {
accessPointId: efsAccessPoint.accessPointId,
iam: "ENABLED",
},
},
},
],
});
Fargate Service
new aws_ecs.FargateService(this, "QdrantService", {
cluster: myECSCluster,
serviceName: `${fargateServiceName}-${label}`,
taskDefinition: qdrantTaskDefinition,
desiredCount: desiredCount,
minHealthyPercent: 0, // Allow the single task to be stopped during deployment
maxHealthyPercent: 200, // Allow 1 new task to start before stopping the old one
vpcSubnets: {
onePerAz: true,
subnetType: aws_ec2.SubnetType.PRIVATE_WITH_EGRESS,
availabilityZones: vpc.availabilityZones,
},
securityGroups: [fargateSvcSecurityGroup],
assignPublicIp: false,
enableExecuteCommand: true, // Enable ECS Exec for debugging
propagateTags: aws_ecs.PropagatedTagSource.SERVICE,
availabilityZoneRebalancing: aws_ecs.AvailabilityZoneRebalancing.ENABLED,
circuitBreaker: { enable: true, rollback: true }, // Deployment circuit breaker
});
Container for the Fargate Task
Refer to article #4 for the
docker buildsteps.
const containerImage = aws_ecs.ContainerImage.fromEcrRepository(ecrRepo, qdrantImageTag);
// Add container to task definition
const dockerLabels = {
"app.cluster": ecsClusterName,
"app.service": fargateServiceName,
"app.task-definition": fargantContainerName, // must match the task family name
"app.component": "vector-database",
"app.version": cdkContextBuildQdrantImageTag,
"app.environment": basicProps.tier,
};
const portMappings = [
{ containerPort: 6333, protocol: aws_ecs.Protocol.TCP },
{ containerPort: 6334, protocol: aws_ecs.Protocol.TCP }, // Qdrant API & gRPC
];
Next Steps
- Clone the GitLab repo containing the CDK construct.
- Build the custom Qdrant image using the Dockerfile from article #4.
- Deploy the stack (
cdk deploy). - Verify the single endpoint resolves to a healthy Qdrant node.
- Configure the ALB and API‑key for dashboard access.
Need help?
- Professional services – Qdrant.tech or my current employer (AWS partner).
- Further reading – Articles #2, #3, and #4 linked above.
Happy building! 🚀
Container Definition
const portMappings = [
{ containerPort: 6333, protocol: aws_ecs.Protocol.TCP, name: 'qdrant-rest', appProtocol: aws_ecs.AppProtocol.http },
{ containerPort: 6334, protocol: aws_ecs.Protocol.TCP, name: 'qdrant-grpc', appProtocol: aws_ecs.AppProtocol.http },
{ containerPort: 6335, protocol: aws_ecs.Protocol.TCP, name: 'qdrant-cluster', appProtocol: aws_ecs.AppProtocol.http },
];
const containerEnvironmentVariables = {
// runtime environment‑variables used by Qdrant‑VectorDB docker‑container
QDRANT__CLUSTER__ENABLED: "true",
QDRANT__SERVICE__API_KEY: nativeApiKeySecret.secretValue.unsafeUnwrap(),
};
qdrantPrimaryNodeTaskDefinition.addContainer('QdrantContainer', {
containerName: taskDefContainerName,
image: containerImage,
user: "1000:1000",
command: ['/bin/sh', '-c', bashScriptFromArticle4],
cpu: constantsCdk.ECS_FARGATE_CPU_SIZING,
memoryLimitMiB: ECS_FARGATE_MEMORY_SIZING_GB * 1024, // in MB
environment: containerEnvironmentVariables,
dockerLabels: { ...dockerLabels, 'app.instance': 'primary-node' },
portMappings,
essential: true,
logging: containerLogs,
healthCheck: commonInsideContainerHealthCheck,
});
Security Group
const fargateSvcSecurityGroup = new aws_ec2.SecurityGroup(
cdkScope,
'QdrantSecurityGroupForFargate',
{
vpc,
securityGroupName: `${ecsClusterName}-SvcInbound-${fargateContainerName}`,
description: `For FARGATE-service - inbound 6333,6334 only, NO outbound. Cluster: ${ecsClusterName}, Container: ${fargateContainerName}`,
allowAllOutbound: false, // Deny/Allow – Explicitly block all outbound traffic
}
);
// Inbound rules for Qdrant ports
fargateSvcSecurityGroup.addIngressRule(
aws_ec2.Peer.ipv4(vpc.vpcCidrBlock),
aws_ec2.Port.tcp(6333),
'Allow inbound traffic on port 6333 (Qdrant REST API)'
);
// Outbound HTTPS for ECR image pulling
fargateSvcSecurityGroup.addEgressRule(
aws_ec2.Peer.anyIpv4(),
aws_ec2.Port.tcp(443),
'Allow outbound HTTPS for ECR image pulling, whether or NOT using VPC Endpoints'
);
// Cluster‑replication traffic
fargateSvcSecurityGroup.addIngressRule(
fargateSvcSecurityGroup,
aws_ec2.Port.tcp(6334),
'Allow INTRA‑CLUSTER Replication traffic on port 6334 (Qdrant gRPC API)'
);
fargateSvcSecurityGroup.addEgressRule(
fargateSvcSecurityGroup,
aws_ec2.Port.tcp(6334),
'Allow INTRA‑CLUSTER Replication traffic on port 6334 (Qdrant gRPC API)'
);
fargateSvcSecurityGroup.addIngressRule(
fargateSvcSecurityGroup,
aws_ec2.Port.tcp(6335),
'Allow INTRA‑CLUSTER Replication traffic on port 6335 (Qdrant Cluster‑Replication traffic)'
);
fargateSvcSecurityGroup.addEgressRule(
fargateSvcSecurityGroup,
aws_ec2.Port.tcp(6335),
'Allow INTRA‑CLUSTER Replication traffic on port 6335 (Qdrant Cluster‑Replication traffic)'
);
// Allow Fargate tasks to access EFS
for (const efsSecurityGroup of efsSecurityGroups) {
efsSecurityGroup.addIngressRule(
fargateSvcSecurityGroup,
aws_ec2.Port.tcp(2049),
'Allow NFS access from Fargate tasks'
);
fargateSvcSecurityGroup.addEgressRule(
efsSecurityGroup,
aws_ec2.Port.tcp(2049),
'Allow NFS access to EFS'
);
}
Other Related Articles
- Get‑Started – article #1 – this one.
- Design – article #2 – full details on the critical design & key requirements that shaped the final implementation.
- Snapshots – article #3 – discussion of snapshots and data restore.
- Dockerfile – A separate GitLab repo contains the full CDK construct.
- Assumption: you’ll be OK to custom‑build the Qdrant container image (using a custom Dockerfile) from Qdrant’s GitHub. See article #4 for a sensible/defensible
Dockerfile.
- Assumption: you’ll be OK to custom‑build the Qdrant container image (using a custom Dockerfile) from Qdrant’s GitHub. See article #4 for a sensible/defensible
End.