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:latestThe 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:
| Label | Purpose |
|---|---|
org.opencontainers.image.created | Build timestamp (UTC) |
org.opencontainers.image.version | Semantic version or tag |
org.opencontainers.image.source | Source repository URL |
org.opencontainers.image.title | Service 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 80ACR 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
| Technique | Impact |
|---|---|
| Alpine base images | ~5MB vs ~80MB for Debian |
| Multi-stage builds | Only runtime artifacts in final image |
.dockerignore | Excludes tests, docs, IDE files |
| Layer caching | Dependencies cached separately from source |
| Non-root user | Security 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