Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .github/workflows/Build-and-push-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,26 @@ jobs:
context: ./
dockerfile: ./agentic-backend/dockerfiles/Dockerfile-prod
image: agentic-backend
tag_rule: "type=match,pattern=code/(v.*),group=1"
target: ""
- name: knowledge-flow-backend
context: ./
dockerfile: ./knowledge-flow-backend/dockerfiles/Dockerfile-prod
image: knowledge-flow-backend
tag_rule: "type=match,pattern=code/(v.*),group=1"
target: "online"
- name: knowledge-flow-backend-offline
context: ./
dockerfile: ./knowledge-flow-backend/dockerfiles/Dockerfile-prod
image: knowledge-flow-backend
tag_rule: "type=match,pattern=code/(v.*),group=1,suffix=-offline"
target: "prod"
- name: frontend
context: ./
dockerfile: ./frontend/dockerfiles/Dockerfile-prod
image: frontend
tag_rule: "type=match,pattern=code/(v.*),group=1"
target: ""

# what each job will play

Expand All @@ -55,15 +67,26 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/thalesgroup/fred-agent/${{ matrix.image }}
tags: |
type=match,pattern=code/(v.*),group=1
tags: ${{ matrix.tag_rule }}

# push on ghcr.io/thalesgroup/fred-agent/myimage:vX.Y.Z
- name: Build and push Docker image
if: matrix.target == ''
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }} # oci://<registry>/<image>:<version>
labels: ${{ steps.meta.outputs.labels }} # many various informations

- name: Build and push Docker image (targeted)
if: matrix.target != ''
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
target: ${{ matrix.target }}
push: true
tags: ${{ steps.meta.outputs.tags }} # oci://<registry>/<image>:<version>
labels: ${{ steps.meta.outputs.labels }} # many various informations
Expand Down Expand Up @@ -92,6 +115,7 @@ jobs:
```
oci://${{ env.REGISTRY }}/thalesgroup/fred-agent/agentic-backend:${{ steps.meta.outputs.version }}
oci://${{ env.REGISTRY }}/thalesgroup/fred-agent/knowledge-flow-backend:${{ steps.meta.outputs.version }}
oci://${{ env.REGISTRY }}/thalesgroup/fred-agent/knowledge-flow-backend:${{ steps.meta.outputs.version }}-offline
oci://${{ env.REGISTRY }}/thalesgroup/fred-agent/frontend:${{ steps.meta.outputs.version }}
```
draft: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ def _path_user_upload() -> str:

@staticmethod
def _path_agent_config_download(agent_id: str, key: str) -> str:
logical_key = (key or "").strip().replace("\\", "/").split("/")[-1]
return f"/storage/agent-config/{agent_id}/{logical_key}"
return f"/storage/agent-config/{agent_id}/{key}"

@staticmethod
def _path_agent_config_upload(agent_id: str) -> str:
Expand Down
12 changes: 12 additions & 0 deletions knowledge-flow-backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,21 @@ docker-run: run-docker ## Backward-compatible alias for run-docker
docker-build: ## Build the Docker image
docker build \
-f $(DOCKERFILE_PATH) \
--target online \
-t $(IMAGE_FULL) \
$(DOCKER_CONTEXT)

.PHONY: docker-build-online
docker-build-online: docker-build ## Backward-compatible alias for docker-build (online image)

.PHONY: docker-build-offline
docker-build-offline: ## Build the offline Docker image
docker build \
-f $(DOCKERFILE_PATH) \
--target prod \
-t $(REGISTRY_URL)/$(REGISTRY_NAMESPACE)/$(IMAGE_NAME):$(IMAGE_TAG)-offline \
$(DOCKER_CONTEXT)

.PHONY: docker-push
docker-push: ## Push the Docker image
docker push $(IMAGE_FULL)
Expand Down
129 changes: 72 additions & 57 deletions knowledge-flow-backend/dockerfiles/Dockerfile-prod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -----------------------------------------------------------------------------
# BUILD STAGE
# BUILD STAGE (shared)
# -----------------------------------------------------------------------------
FROM mirror.gcr.io/python:3.12.8-slim AS builder
ARG USER_NAME=fred-user
Expand All @@ -20,7 +20,6 @@ RUN curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh && \
# Create virtualenv manually (outside project for portability)
ENV UV_PROJECT_ENVIRONMENT=/app/venv
ENV PATH="/app/venv/bin:$PATH"
ENV TIKTOKEN_CACHE_DIR=/app/encodings

# Set working directory
WORKDIR /app
Expand All @@ -37,25 +36,19 @@ COPY fred-core /fred-core
# Install dependencies using make with both VENV and TARGET properly set
RUN make dev

# Prepare local encodings directory
RUN mkdir -p $TIKTOKEN_CACHE_DIR

# Download encodings for offline usage
RUN python /scripts/download_encodings.py https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken

# Copy projets
# Copy project
COPY knowledge-flow-backend/. /app/

# -----------------------------------------------------------------------------
# FINAL STAGE: PROD IMAGE
# RUNTIME BASE STAGE (shared by online/offline)
# -----------------------------------------------------------------------------
FROM mirror.gcr.io/python:3.12.8-slim
FROM mirror.gcr.io/python:3.12.8-slim AS runtime-base
ARG USER_NAME=fred-user
ARG USER_ID=1000
ARG GROUP_ID=1000
ARG HF_OFFLINE_STRICT=1

# System deps (minimal)
# System deps (minimal runtime)
RUN apt-get update && \
apt-get install -y \
curl \
Expand All @@ -80,53 +73,8 @@ RUN groupadd -g ${GROUP_ID} ${USER_NAME} && \

# Copy virtualenv from build stage
COPY --from=builder --chown=${USER_ID}:${GROUP_ID} /app/venv /app/venv
COPY --from=builder --chown=${USER_ID}:${GROUP_ID} /app/encodings /app/encodings

ENV PATH="/app/venv/bin:$PATH"
ENV TIKTOKEN_CACHE_DIR=/app/encodings
ENV DOCLING_ARTIFACTS_PATH=/app/docling-artifacts

# Hugging Face / reranker cache layout
ENV CACHE_HOME=/home/${USER_NAME}/.cache
ENV RERANKER_MODEL=cross-encoder/ms-marco-MiniLM-L-12-v2
ENV RERANKER_MODEL_CACHE=${CACHE_HOME}/huggingface/hub
ENV HF_HOME=${CACHE_HOME}
ENV HF_HUB_CACHE=${RERANKER_MODEL_CACHE}
ENV HUGGINGFACE_HUB_CACHE=${RERANKER_MODEL_CACHE}
ENV SENTENCE_TRANSFORMERS_HOME=${RERANKER_MODEL_CACHE}
ENV TRANSFORMERS_CACHE=${RERANKER_MODEL_CACHE}
RUN mkdir -p ${CACHE_HOME} ${RERANKER_MODEL_CACHE} ${DOCLING_ARTIFACTS_PATH} && \
chown -R ${USER_ID}:${GROUP_ID} ${CACHE_HOME} ${DOCLING_ARTIFACTS_PATH}

# Warm the cross-encoder cache during build (faster startup, offline-friendly runtime)
USER ${USER_NAME}
RUN python -c "\
from sentence_transformers import CrossEncoder; \
import os; \
CrossEncoder(model_name_or_path=os.environ['RERANKER_MODEL'], cache_folder=os.environ['RERANKER_MODEL_CACHE'])"

# Preload Docling artifacts (layout/table + EasyOCR) for offline-friendly runtime
RUN python -c "\
from pathlib import Path; \
import os; \
from docling.utils.model_downloader import download_models; \
download_models( \
output_dir=Path(os.environ['DOCLING_ARTIFACTS_PATH']), \
progress=False, \
with_layout=True, \
with_tableformer=True, \
with_easyocr=True, \
with_code_formula=False, \
with_picture_classifier=False, \
with_smolvlm=False, \
with_smoldocling=False, \
with_smoldocling_mlx=False, \
with_granite_vision=False \
)"
# Runtime offline policy for Hugging Face based dependencies.
ENV HF_HUB_OFFLINE=${HF_OFFLINE_STRICT}
ENV TRANSFORMERS_OFFLINE=${HF_OFFLINE_STRICT}
ENV HF_HUB_DISABLE_TELEMETRY=1

# Set working directory
WORKDIR /app
Expand All @@ -139,9 +87,76 @@ COPY --chown=${USER_ID}:${GROUP_ID} scripts /scripts
# Set environment
ENV PYTHONPATH=/app

# -----------------------------------------------------------------------------
# ONLINE IMAGE
# -----------------------------------------------------------------------------
FROM runtime-base AS online
ARG USER_NAME=fred-user

# Run as non-root
USER ${USER_NAME}

# Expose Fast API port
EXPOSE 8111

# Entrypoint without make
ENTRYPOINT ["uvicorn", "knowledge_flow_backend.main:create_app", "--factory"]
CMD ["--port", "8111", "--host", "0.0.0.0", "--log-level", "info", "--loop", "asyncio"]

# -----------------------------------------------------------------------------
# OFFLINE IMAGE (prod)
# -----------------------------------------------------------------------------
FROM online AS prod
ARG USER_NAME=fred-user
ARG USER_ID=1000
ARG GROUP_ID=1000

# Switch to root to prepare writable cache directories
USER root

ENV TIKTOKEN_CACHE_DIR=/app/encodings
ENV DOCLING_ARTIFACTS_PATH=/app/docling-artifacts

# Hugging Face / reranker cache layout
ENV CACHE_HOME=/home/${USER_NAME}/.cache
ENV RERANKER_MODEL=cross-encoder/ms-marco-MiniLM-L-12-v2
ENV RERANKER_MODEL_CACHE=${CACHE_HOME}/huggingface/hub
ENV HF_HOME=${CACHE_HOME}
ENV HF_HUB_CACHE=${RERANKER_MODEL_CACHE}
ENV HUGGINGFACE_HUB_CACHE=${RERANKER_MODEL_CACHE}
ENV SENTENCE_TRANSFORMERS_HOME=${RERANKER_MODEL_CACHE}
ENV TRANSFORMERS_CACHE=${RERANKER_MODEL_CACHE}

# Prepare local cache directories for offline runtime
RUN mkdir -p ${TIKTOKEN_CACHE_DIR} ${CACHE_HOME} ${RERANKER_MODEL_CACHE} ${DOCLING_ARTIFACTS_PATH} && \
chown -R ${USER_ID}:${GROUP_ID} ${TIKTOKEN_CACHE_DIR} ${CACHE_HOME} ${DOCLING_ARTIFACTS_PATH}

USER ${USER_NAME}

# Download encodings for offline usage
RUN python /scripts/download_encodings.py https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken

# Warm the cross-encoder cache during build (faster startup, offline-friendly runtime)
RUN python -c "\
from sentence_transformers import CrossEncoder; \
import os; \
CrossEncoder(model_name_or_path=os.environ['RERANKER_MODEL'], cache_folder=os.environ['RERANKER_MODEL_CACHE'])"

# Preload Docling artifacts (layout/table + EasyOCR) for offline-friendly runtime
RUN python -c "\
from pathlib import Path; \
import os; \
from docling.utils.model_downloader import download_models; \
download_models( \
output_dir=Path(os.environ['DOCLING_ARTIFACTS_PATH']), \
progress=False, \
with_layout=True, \
with_tableformer=True, \
with_easyocr=True, \
with_code_formula=False, \
with_picture_classifier=False, \
with_smolvlm=False, \
with_smoldocling=False, \
with_smoldocling_mlx=False, \
with_granite_vision=False \
)"