MATIH Platform is in active MVP development. Documentation reflects current implementation status.
18. CI/CD & Build System
Base Images

Base Docker Images

MATIH maintains a set of curated base Docker images that provide consistent, secure, and optimized foundations for all service containers. These base images are built and managed through scripts/tools/build-base-images.sh and stored in the Azure Container Registry at matihlabsacr.azurecr.io.


Base Image Inventory

ImageTagBasePurposeApproximate Size
matih/base-java1.0.0eclipse-temurin:21-jre-alpineJava Spring Boot services~200 MB
matih/base-python-ml1.0.0python:3.11-slim-bookwormPython AI/ML services~600 MB
matih/base-node1.0.0node:20-alpineNode.js build + runtime~180 MB
matih/base-nginx1.25-alpinenginx:1.25-alpineFrontend static file serving~40 MB

Build Process

Building Base Images

# Build all base images
./scripts/tools/build-base-images.sh
 
# Build specific base image
./scripts/tools/build-base-images.sh --image base-java
 
# Build and push to registry
./scripts/tools/build-base-images.sh --push

Build Script Flow

scripts/tools/build-base-images.sh
  |
  +-- Build base-java:1.0.0
  |     FROM eclipse-temurin:21-jre-alpine
  |     + security patches + CA certificates + non-root user
  |
  +-- Build base-python-ml:1.0.0
  |     FROM python:3.11-slim-bookworm
  |     + ML libraries + C extensions + non-root user
  |
  +-- Build base-node:1.0.0
  |     FROM node:20-alpine
  |     + build tools + non-root user
  |
  +-- Build base-nginx:1.25-alpine
  |     FROM nginx:1.25-alpine
  |     + security headers + non-root config
  |
  +-- Push all to matihlabsacr.azurecr.io/matih/

base-java:1.0.0

The Java base image provides a minimal JRE runtime for Spring Boot services.

Dockerfile

FROM eclipse-temurin:21-jre-alpine
 
# Install security updates
RUN apk update && apk upgrade --no-cache && \
    apk add --no-cache \
      ca-certificates \
      tzdata \
      curl \
      tini
 
# Create non-root user
RUN addgroup -g 1000 matih && \
    adduser -u 1000 -G matih -D -h /app matih
 
# Set timezone
ENV TZ=UTC
 
# JVM defaults (overridable via JAVA_OPTS)
ENV JAVA_OPTS="-XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:+UseStringDeduplication \
  -XX:+OptimizeStringConcat \
  -Djava.security.egd=file:/dev/./urandom \
  -Dspring.output.ansi.enabled=NEVER"
 
# Health check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:${SERVER_PORT:-8080}/actuator/health || exit 1
 
WORKDIR /app
USER matih
 
# Use tini as init process for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["java", "-jar", "app.jar"]

Key Design Decisions

DecisionRationale
Alpine-basedMinimal attack surface (~5 MB base OS)
JRE only (not JDK)No compiler needed at runtime
G1GC as defaultBest balance of throughput and latency
tini as initProper PID 1 signal handling, zombie process reaping
Non-root user (UID 1000)Security: matches Kubernetes securityContext
urandom entropy sourceAvoids JVM startup delay on entropy-starved containers

JVM Tuning Profiles

Service charts override JAVA_OPTS based on workload profile:

# API service (low latency)
env:
  - name: JAVA_OPTS
    value: "-Xms512m -Xmx768m -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
 
# Batch processing (high throughput)
env:
  - name: JAVA_OPTS
    value: "-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:ParallelGCThreads=4"
 
# Config service (minimal footprint)
env:
  - name: JAVA_OPTS
    value: "-Xms256m -Xmx512m -XX:+UseSerialGC"

base-python-ml:1.0.0

The Python ML base image provides a runtime with pre-installed system dependencies for machine learning libraries.

Dockerfile

FROM python:3.11-slim-bookworm
 
# Install system dependencies for ML libraries
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    gcc \
    g++ \
    libpq-dev \
    libffi-dev \
    libssl-dev \
    curl \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*
 
# Install common ML/data dependencies
RUN pip install --no-cache-dir \
    numpy==1.26.3 \
    pandas==2.1.4 \
    scipy==1.11.4 \
    scikit-learn==1.3.2 \
    tokenizers==0.15.0
 
# Create non-root user
RUN groupadd -g 1000 matih && \
    useradd -u 1000 -g matih -m -d /app matih
 
# Set environment
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONFAULTHANDLER=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1
 
WORKDIR /app
USER matih
 
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:${PORT:-8000}/api/v1/health || exit 1
 
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

Pre-installed Dependencies

PackageVersionRationale
numpy1.26.3Numerical computing foundation
pandas2.1.4DataFrame operations
scipy1.11.4Scientific computing
scikit-learn1.3.2ML utilities (preprocessing, metrics)
tokenizers0.15.0Fast text tokenization (HuggingFace)

These packages are installed in the base image because:

  1. They require C extension compilation (slow to install)
  2. They are used by multiple Python services
  3. Pre-installing them dramatically reduces per-service build times

Python Environment Variables

VariableValuePurpose
PYTHONDONTWRITEBYTECODE1Skip .pyc file generation (read-only filesystem)
PYTHONUNBUFFERED1Real-time log output (no buffering)
PYTHONFAULTHANDLER1Print traceback on segfault
PIP_NO_CACHE_DIR1Reduce image size
PIP_DISABLE_PIP_VERSION_CHECK1Faster pip operations

base-node:1.0.0

The Node.js base image provides a build environment for React/Vite frontend applications.

Dockerfile

FROM node:20-alpine
 
# Install build dependencies
RUN apk add --no-cache \
    python3 \
    make \
    g++ \
    curl \
    ca-certificates
 
# Create non-root user
RUN addgroup -g 1000 matih && \
    adduser -u 1000 -G matih -D -h /app matih
 
# Configure npm
RUN npm config set fund false && \
    npm config set audit false && \
    npm config set update-notifier false
 
WORKDIR /app
 
# Use non-root for builds
USER matih
 
CMD ["node"]

Usage Pattern

The Node.js base image is primarily used as a build stage. The final runtime stage uses the NGINX base:

# Build stage
FROM matihlabsacr.azurecr.io/matih/base-node:1.0.0 AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
 
# Runtime stage
FROM matihlabsacr.azurecr.io/matih/base-nginx:1.25-alpine
COPY --from=builder /build/dist /usr/share/nginx/html

base-nginx:1.25-alpine

The NGINX base image serves static frontend assets with security hardening.

Dockerfile

FROM nginx:1.25-alpine
 
# Install ca-certificates
RUN apk add --no-cache ca-certificates
 
# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf
 
# Security: Remove server version
RUN sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf
 
# Default security headers config
COPY security-headers.conf /etc/nginx/conf.d/security-headers.conf
 
# Create non-root user
RUN addgroup -g 1000 matih && \
    adduser -u 1000 -G matih -D matih && \
    chown -R matih:matih /var/cache/nginx /var/log/nginx /etc/nginx/conf.d && \
    touch /var/run/nginx.pid && \
    chown matih:matih /var/run/nginx.pid
 
USER matih
 
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Security Headers

# security-headers.conf
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.matih.ai wss://*.matih.ai" always;

Multi-Stage Build Pattern

Every MATIH service follows a multi-stage Docker build pattern:

+-------------------+    +-------------------+    +-------------------+
|   Stage 1: Deps   |    |   Stage 2: Build  |    |  Stage 3: Runtime |
|                   |    |                   |    |                   |
| FROM base-image   |--->| FROM base-image   |--->| FROM base-image   |
| COPY manifest     |    | COPY --from=deps  |    | COPY --from=build |
| RUN install       |    | COPY source       |    | USER non-root     |
|                   |    | RUN compile       |    | CMD start          |
+-------------------+    +-------------------+    +-------------------+
     (Cached)                 (Rebuilt on             (Minimal runtime)
                               code change)

Benefits

BenefitDescription
Smaller imagesRuntime image contains only compiled artifacts, not build tools
Faster buildsDependency layer is cached; only source code changes trigger rebuild
SecurityBuild tools, compilers, and source code excluded from runtime
ConsistencySame base image across all services of the same type

Image Security Scanning

All base images and service images are scanned for vulnerabilities:

# Scan with Trivy
trivy image matihlabsacr.azurecr.io/matih/base-java:1.0.0
 
# Scan with Azure Defender
az acr repository show-tags \
  --name matihlabsacr \
  --repository matih/base-java \
  --detail

Vulnerability Policy

SeverityPolicyAction
CriticalBlock deploymentMust be fixed before merge
HighBlock deploymentMust be fixed within 7 days
MediumWarningFix in next release cycle
LowInformationalTrack in backlog

Base Image Update Process

Scheduled Updates

Base images are rebuilt monthly to incorporate security patches:

  1. Pull latest upstream image (e.g., eclipse-temurin:21-jre-alpine)
  2. Apply MATIH customizations (user, packages, config)
  3. Run vulnerability scan
  4. Push with new patch version (e.g., 1.0.1)
  5. Update service Dockerfiles to reference new base
  6. Rebuild and test all services
  7. Deploy via CD pipeline

Emergency Security Patch

For critical CVEs affecting base images:

  1. Identify affected base image
  2. Rebuild immediately with patched upstream
  3. Push with incremented patch version
  4. Trigger rebuild of all affected service images
  5. Deploy hotfix via service-build-deploy.sh

Troubleshooting

Common Base Image Issues

IssueCauseResolution
"permission denied" at runtimeUID mismatch between base and chartEnsure securityContext.runAsUser matches Dockerfile USER
C extension build failsMissing system libraries in baseAdd required -dev packages to base Dockerfile
Image too largeUnnecessary files includedAdd .dockerignore; verify multi-stage COPY
"exec format error"Architecture mismatch (arm64 vs amd64)Build multi-arch images with buildx
Health check failsWrong port or pathVerify HEALTHCHECK CMD matches service configuration

Next Steps