Vector-Database: Qdrant-cluster - Dockerfile
Source: Dev.to
Why on earth did I choose to create my own Dockerfile?
- Technically, yes, it’s “my own”. But it’s not a brand‑new Dockerfile – I just slashed‑and‑re‑slashed Qdrant’s official
Dockerfileuntil I got it to this point. It can be simplified further by removing GPU support. - I initially tried using Qdrant’s as‑is
Dockerfile, but that made the CDK design extremely complicated. Adding a “starter” bash script for the container image gave me a simple CDK. - There was no way I was going to trust all the third‑party images and packages that Qdrant installs in their Dockerfile.
- My Dockerfile only relies on
debian-slim,cargo‑chef, andmold. That’s it. I also addnslookuponly to simplify the cluster‑deployment design. - All my base images must be verified images from
public.ecr.aws. A religious decision – Docker Hub images over my dead body.
What if Qdrant’s official Docker image is mandatory?
Perhaps your enterprise mandates the use of Qdrant’s official ready‑to‑use Docker images, rather than a Dockerfile from a “suspicious no‑name guy” (ahem… me).
Option 1: Use the official image as a base and incorporate the bash script below as the container’s entrypoint.
Option 2: Deploy three Fargate services (to ensure two shards replicated across 2‑or‑3 AZs). Details are out of scope for this article set. I got this working initially, but the resulting set of AWS resources was unmaintainable. That led to the drastically simplified design of a single Fargate service with one task and one container – which is what this article set is about.
How‑to
- Copy the bash script (below) into the cloned Qdrant repository. It will be the container’s entrypoint.
- Copy the custom Dockerfile (below) into the same repository.
- Build the container image.
- Push the image to an ECR repository.
Explaining the “entrypoint” Bash script
The script performs a DNS lookup of qdrant-cluster.my-ecs-cluster.local (stored in the variable QdrantClusterFQDN).
| DNS result | Command executed |
|---|---|
| No records | ./qdrant --bootstrap http://${QdrantClusterFQDN}:6335 |
| One or more records | ./qdrant --uri http://${QdrantClusterFQDN}:6335 |
| Any other situation | Dump the dig output and exit with an error. |
Could Qdrant Corp have baked this logic into their binary? It would simplify cluster setup, but then their livelihood would be gone.
Why is this needed?
According to the official documentation, the first node must be started as:
./qdrant --bootstrap http://${qdrantPrimaryFQDN}:6335
All subsequent nodes must be started as:
./qdrant --uri http://${qdrantPrimaryFQDN}:6335
If the first node fails and a replacement node is launched automatically, using --bootstrap again would break the cluster. The script handles these edge cases gracefully.
Bash script
Note: Replace
my-ecs-clusterwith the name of your ECS cluster.
The script works forarm64with or without an NVIDIA GPU.
#!/bin/bash -e
# ----------------------------------------------------------------------
# Configuration
# ----------------------------------------------------------------------
MyClusterName="my-ecs-cluster"
QdrantClusterFQDN="qdrant-cluster.${MyClusterName}.local"
# ----------------------------------------------------------------------
# Install DNS utilities (only needed the first time the image runs)
# ----------------------------------------------------------------------
echo "Installing DNS utilities..."
apt-get update && \
apt-get install -y --no-install-recommends dnsutils && \
apt-get autoremove -y && \
apt-get autoclean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* /var/tmp/*
# ----------------------------------------------------------------------
# Perform DNS lookup and decide how to start Qdrant
# ----------------------------------------------------------------------
echo "Performing DNS lookup for ${QdrantClusterFQDN}..."
if DNS_RESULT=$(dig +short "$QdrantClusterFQDN" A 2>/dev/null) && [ -n "$DNS_RESULT" ]; then
RECORD_COUNT=$(echo "$DNS_RESULT" | grep -cE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || echo 0)
if [ "$RECORD_COUNT" -eq 0 ]; then
echo "No valid IP records found. Bootstrapping a brand‑new Qdrant cluster..."
exec ./qdrant --bootstrap "http://${QdrantClusterFQDN}:6335"
else
echo "Found ${RECORD_COUNT} DNS record(s). Joining existing cluster..."
exec ./qdrant --uri "http://${QdrantClusterFQDN}:6335"
fi
else
echo "dig command failed or returned empty result. Full output:"
dig "$QdrantClusterFQDN" A
echo "FATAL: Unable to determine whether this Qdrant node belongs to a cluster."
exit 1
fi
Dockerfile
For
arm64CPU architecture.
Warning: Even on an XLARGE CodeBuild instance (32 vCPUs, 128 GB RAM) the build takes ~20 minutes.
# ----------------------------------------------------------------------
# Build‑time arguments
# ----------------------------------------------------------------------
ARG GPU # Set to `nvidia` or `amd` to enable GPU support (optional)
# ----------------------------------------------------------------------
# Base image – official Rust image from AWS ECR Public Gallery
# ----------------------------------------------------------------------
FROM public.ecr.aws/docker/library/rust:1.90-bookworm AS base
# Install cargo‑chef manually for better security control
RUN cargo install cargo-chef --locked
# ----------------------------------------------------------------------
# Planner stage – generate a reproducible build recipe
# ----------------------------------------------------------------------
FROM base AS planner
WORKDIR /qdrant
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# ----------------------------------------------------------------------
# Builder stage – compile Qdrant
# ----------------------------------------------------------------------
FROM base AS builder
WORKDIR /qdrant
# Install build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
clang lld cmake protobuf-compiler libprotobuf-dev \
protobuf-compiler-grpc jq pkg-config gcc g++ libc6-dev \
libunwind-dev curl ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/debconf/* && \
rustup component add rustfmt
# ----------------------------------------------------------------------
# ARG/ENV workaround for backward‑compatible `docker build`
# ----------------------------------------------------------------------
ARG BUILDPLATFORM
ARG TARGETPLATFORM
ARG TARGETARCH
ARG TARGETVARIANT
ENV BUILDPLATFORM=${BUILDPLATFORM}
ENV TARGETPLATFORM=${TARGETPLATFORM}
ENV TARGETARCH=${TARGETARCH}
ENV TARGETVARIANT=${TARGETVARIANT}
# ----------------------------------------------------------------------
# Re‑use the recipe from the planner stage and build the binary
# ----------------------------------------------------------------------
COPY --from=planner /qdrant/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
# ----------------------------------------------------------------------
# Final stage – minimal runtime image
# ----------------------------------------------------------------------
FROM public.ecr.aws/debian/debian:bookworm-slim AS runtime
# Install runtime dependencies (including DNS utilities for the entrypoint)
RUN apt-get update && \
apt-get install -y --no-install-recommends dnsutils ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Copy the compiled binary from the builder stage
COPY --from=builder /qdrant/target/release/qdrant /usr/local/bin/qdrant
# Copy the entrypoint script
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Set the entrypoint
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Dockerfile Segment
[ "$USER_ID" != 0 ]; then \
groupadd --gid "$USER_ID" qdrant; \
useradd --uid "$USER_ID" --gid "$USER_ID" -m qdrant; \
mkdir -p "$APP"/storage "$APP"/snapshots; \
chown -R "$USER_ID:$USER_ID" "$APP"; \
fi
COPY --from=builder --chown=$USER_ID:$USER_ID /qdrant/qdrant "$APP"/qdrant
COPY --from=builder --chown=$USER_ID:$USER_ID /qdrant/qdrant.spdx.json "$APP"/qdrant.spdx.json
COPY --from=builder --chown=$USER_ID:$USER_ID /qdrant/config "$APP"/config
COPY --from=builder --chown=$USER_ID:$USER_ID /qdrant/tools/entrypoint.sh "$APP"/entrypoint.sh
COPY --from=builder --chown=$USER_ID:$USER_ID /static "$APP"/static
WORKDIR "$APP"
USER "$USER_ID:$USER_ID"
ENV TZ=Etc/UTC \
RUN_MODE=production
EXPOSE 6333
EXPOSE 6334
LABEL org.opencontainers.image.title="Qdrant"
LABEL org.opencontainers.image.description="Official Qdrant image"
LABEL org.opencontainers.image.url="https://qdrant.com/"
LABEL org.opencontainers.image.documentation="https://qdrant.com/docs"
LABEL org.opencontainers.image.source="https://github.com/qdrant/qdrant"
LABEL org.opencontainers.image.vendor="Qdrant"
# Leaving the last CMD intact, since we will be overriding entrypoint.
CMD ["./entrypoint.sh"]
Appendix
Get‑Started Articles
- Article #1 – Vector Database Qdrant Cluster on ECS Fargate
- Article #2 – Design Vector Database Qdrant Cluster (full details on critical design & key requirements)
- Article #3 – Snapshots, Data Restore, Vector Database Qdrant Cluster
- Article #4 – Vector Database Qdrant Cluster Dockerfile (a sensible/defensible Dockerfile)
Additional Resources
- A separate GitLab repository contains the full CDK construct.
- Assumption: You are okay with custom‑building the Qdrant container image (using a custom Dockerfile) from Qdrant’s GitHub repository.