Vector-Database: Qdrant-cluster on ECS-Fargate

Published: (December 30, 2025 at 10:36 PM EST)
5 min read
Source: Dev.to

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.

Qdrant cluster diagram

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 build steps.

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

  1. Clone the GitLab repo containing the CDK construct.
  2. Build the custom Qdrant image using the Dockerfile from article #4.
  3. Deploy the stack (cdk deploy).
  4. Verify the single endpoint resolves to a healthy Qdrant node.
  5. 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'
  );
}
  • Get‑Startedarticle #1 – this one.
  • Designarticle #2 – full details on the critical design & key requirements that shaped the final implementation.
  • Snapshotsarticle #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.

End.

Back to Blog

Related posts

Read more »

AI SEO agencies Nordic

!Cover image for AI SEO agencies Nordichttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads...