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

Docker Images and Base Images

The MATIH platform uses multi-stage Docker builds with shared base images to optimize build times and ensure consistent runtime environments. Base images are built in Stage 02 of the CD pipeline via scripts/tools/build-base-images.sh.


Base Image Architecture

Base Images (built first, shared across services)
├── matih/base-java:latest       (Eclipse Temurin 21 JRE Alpine)
├── matih/base-python:latest     (Python 3.11 Slim with common libs)
├── matih/base-node:latest       (Node.js 20 Alpine)
└── matih/base-nginx:latest      (Nginx Alpine for frontends)

Service Images (built from base images)
├── matih/iam-service:v1.2.3     (FROM matih/base-java)
├── matih/ai-service:v1.2.3      (FROM matih/base-python)
├── matih/bi-workbench:v1.2.3    (FROM matih/base-nginx)
└── ...

Image Tagging Strategy

Every image receives multiple tags:

# Version-specific tag
docker tag matih/iam-service:latest matih/iam-service:v1.2.3
 
# Git SHA tag
docker tag matih/iam-service:latest matih/iam-service:sha-abc1234
 
# Latest tag (always present)
docker tag matih/iam-service:latest matih/iam-service:latest

The build script applies tags automatically:

docker build \
    --tag "${REGISTRY}/${service_name}:${IMAGE_TAG}" \
    --tag "${REGISTRY}/${service_name}:latest" \
    --build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --build-arg VERSION="${IMAGE_TAG}" \
    --label "org.opencontainers.image.created=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --label "org.opencontainers.image.version=${IMAGE_TAG}" \
    "$service_path"

OCI Labels

All images include OCI-compliant labels for traceability:

LabelPurpose
org.opencontainers.image.createdBuild timestamp (UTC)
org.opencontainers.image.versionSemantic version or tag
org.opencontainers.image.sourceSource repository URL
org.opencontainers.image.titleService name

Multi-Stage Build Patterns

Java Services

# Stage 1: Build
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src/ src/
RUN mvn clean package -DskipTests
 
# Stage 2: Runtime
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S matih && adduser -S matih -G matih
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
USER matih
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]

Python Services

# Stage 1: Dependencies
FROM python:3.11-slim AS builder
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir --prefix=/install .
 
# Stage 2: Runtime
FROM python:3.11-slim
RUN useradd -r -s /bin/false matih
WORKDIR /app
COPY --from=builder /install /usr/local
COPY src/ src/
USER matih
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

Frontend Applications

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
 
# Stage 2: Serve
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

ACR Build and Publish

In the CD pipeline, images are built and published to Azure Container Registry:

# Login to ACR
az acr login --name ${ACR_NAME}
 
# Build and push
docker build -t ${ACR_URL}/${service_name}:${IMAGE_TAG} .
docker push ${ACR_URL}/${service_name}:${IMAGE_TAG}

The service-build-deploy.sh tool provides an alternative single-service workflow that clones fresh source, builds without Docker cache, and pushes to ACR.


Image Size Optimization

TechniqueImpact
Alpine base images~5MB vs ~80MB for Debian
Multi-stage buildsOnly runtime artifacts in final image
.dockerignoreExcludes tests, docs, IDE files
Layer cachingDependencies cached separately from source
Non-root userSecurity hardening (runs as matih user)

Registry Configuration

# Default registry (for local development)
REGISTRY="ghcr.io/matih"
 
# Azure Container Registry (for deployment)
ACR_NAME="matihlabsacr"
ACR_URL="${ACR_NAME}.azurecr.io"
 
# Override via environment
REGISTRY=myregistry.azurecr.io ./scripts/build.sh --push