Skip to content
Merged
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
39 changes: 31 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
# OpenUTM Verification Environment Configuration
# Copy this file to .env and customize the values for your environment
# Use with: docker compose --env-file .env up

# =============================================================================
# Configuration
# =============================================================================
# Path to YAML config file inside the container
OPENUTM_CONFIG_PATH=/app/config/default.yaml

# =============================================================================
# Flight Blender Configuration
FLIGHT_BLENDER_URL=http://localhost:8000
# =============================================================================
# Flight Blender endpoint URL
# - Local docker: http://host.docker.internal:8000 (macOS/Windows)
# - Local native: http://localhost:8000
# - Remote: https://blender.example.com
FLIGHT_BLENDER_URL=http://host.docker.internal:8000

# =============================================================================
# Logging Configuration
# =============================================================================
# Log level: DEBUG, INFO, WARNING, ERROR
LOG_LEVEL=INFO

# Environment
ENVIRONMENT=development

# =============================================================================
# Docker Configuration
# =============================================================================
# User ID mapping (for proper file permissions on mounted volumes)
HOST_UID=1000
HOST_GID=1000

# Compose project name
COMPOSE_PROJECT_NAME=openutm-verification

# Build Configuration
# BuildKit for faster builds
DOCKER_BUILDKIT=1
UV_COMPILE_BYTECODE=1 # Set to 1 for production builds, 0 for development
UV_LINK_MODE=copy

# Development Configuration
# =============================================================================
# Build Configuration
# =============================================================================
# UV settings for Python dependency manager
UV_COMPILE_BYTECODE=1 # Set to 0 for faster builds during development
UV_LINK_MODE=copy
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ repos:
- id: check-shebang-scripts-are-executable
- id: mixed-line-ending
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
rev: v1.5.6
hooks:
- id: remove-crlf
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.5
rev: v0.15.11
hooks:
- id: ruff-check
types_or: [python, pyi]
Expand All @@ -35,7 +35,7 @@ repos:
hooks:
- id: blacken-docs
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
rev: v2.4.2
hooks:
- id: codespell
additional_dependencies:
Expand Down
118 changes: 58 additions & 60 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,111 +1,109 @@
# Multi-stage Dockerfile for OpenUTM Verification
# Single image: FastAPI backend on :8989 also serves the built React frontend.
#
# For local development, skip Docker and run two processes directly:
# 1. uv run uvicorn openutm_verification.server.main:app --reload --port 8989
# 2. cd web-editor && npm run dev # Vite on :5173, proxies API to :8989

# --- UI Builder Stage ---
FROM node:20-slim AS ui-builder
FROM node:25-slim AS ui-builder
WORKDIR /app/web-editor

# Install dependencies first for better layer caching
COPY web-editor/package.json web-editor/package-lock.json ./
RUN npm ci

# Copy source and build
COPY web-editor/ .
RUN npm run build

# --- Builder Stage ---
# --- Backend Builder Stage ---
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

# Build arguments
ARG UV_COMPILE_BYTECODE=1
ARG UV_LINK_MODE=copy

# Install necessary build-time dependencies for compiling Python extensions
# `git` is required because one dependency (`cam-track-gen`) is fetched from
# a Git repo (see `pyproject.toml`); uv shells out to the `git` CLI to clone
# it. All other dependencies resolve to prebuilt wheels, so no C toolchain
# is needed.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Set working directory for the application in the builder image
WORKDIR /app

# Configure UV for optimal Docker layer caching and performance
ENV UV_COMPILE_BYTECODE=${UV_COMPILE_BYTECODE}
ENV UV_LINK_MODE=${UV_LINK_MODE}
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=${UV_COMPILE_BYTECODE} \
UV_LINK_MODE=${UV_LINK_MODE} \
PYTHONUNBUFFERED=1

# --- Dependency Installation Layer ---
# Copy dependency files first for better layer caching
# Resolve dependencies first (cached unless lockfile changes).
# `docs/scenarios` is force-included into the wheel by hatch, so the docs
# tree must be present at build time.
COPY pyproject.toml uv.lock ./
COPY docs ./docs
COPY scenarios ./scenarios

# Install project dependencies using uv sync with cache mount for faster builds
# --frozen: ensures reproducible builds from uv.lock
# --no-install-project: skips installing the project itself (we do this later)
# --no-dev: excludes development dependencies for production builds
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev

# --- Application Installation Layer ---
# Copy the rest of the application source code
# Build & install just the project wheel (no editable, no tests).
COPY LICENSE README.md ./
COPY src ./src
COPY tests ./tests
RUN uv pip install --no-deps .

# Install the project itself in the builder stage
# --no-deps: Dependencies are already installed, skip resolution
RUN uv pip install --no-deps . && rm -f LICENSE
# --- Production Runtime Stage ---
# Plain python image — `uv` is only needed at build time, so dropping it
# from the runtime keeps the final image smaller.
FROM python:3.12-slim-bookworm AS production

# --- Production Stage ---
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS production

# Build arguments for production stage
ARG APP_USER=appuser
ARG APP_GROUP=appgrp
ARG UID=1000
ARG GID=1000

# Install minimal runtime dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
tzdata \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# No extra runtime apt packages — Python's stdlib covers the healthcheck,
# and `tzdata` ships with the Python image.

# Configure environment
ENV PYTHONUNBUFFERED=1
ENV TZ=UTC
ENV PATH="/app/.venv/bin:$PATH"
ENV WEB_EDITOR_PATH=/app/web-editor
ENV SCENARIOS_PATH=/app/scenarios
ENV DOCS_PATH=/app/docs
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
TZ=UTC \
PATH="/app/.venv/bin:$PATH" \
WEB_EDITOR_PATH=/app/web-editor \
SCENARIOS_PATH=/app/scenarios \
DOCS_PATH=/app/docs \
OPENUTM_CONFIG_PATH=config/default.yaml \
REPORTS_DIR=reports

# Create non-root user and group for enhanced security
# Non-root user
RUN (getent group "${GID}" || groupadd -g "${GID}" "${APP_GROUP}") \
&& useradd -u "${UID}" -g "${GID}" -s /bin/sh -m "${APP_USER}"

# Copy application artifacts from builder stage
COPY --chown=${UID}:${GID} --from=builder /app /app
WORKDIR /app

# Copy UI artifacts from ui-builder stage
# Copy only what's needed at runtime: the venv (project + deps installed,
# including the package's bundled `assets/` data files), the docs/scenarios
# trees used as defaults, and the built UI bundle. The `src/` tree is NOT
# copied — the wheel installed into the venv contains everything the app
# imports, and sample data files live under `config/` (bind-mounted).
COPY --chown=${UID}:${GID} --from=builder /app/.venv /app/.venv
COPY --chown=${UID}:${GID} docs /app/docs
COPY --chown=${UID}:${GID} scenarios /app/scenarios
COPY --chown=${UID}:${GID} --from=ui-builder /app/web-editor/dist /app/web-editor/dist

# Set working directory
WORKDIR /app

# Create necessary directories with proper permissions
# Volume targets (config + reports are normally bind-mounted in compose).
RUN mkdir -p /app/config /app/reports \
&& chown -R ${UID}:${GID} /app/config /app/reports

# Switch to non-root user
USER ${UID}:${GID}

# Expose the server port
EXPOSE 8989

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sys; print('OK'); sys.exit(0)" || exit 1

# Define the entrypoint for the container
ENTRYPOINT ["openutm-verify"]
CMD python -c "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8989/health', timeout=5).status == 200 else 1)" || exit 1

# Set the default command with arguments
CMD ["--config", "config/default.yaml"]
# Backend serves both API and frontend (StaticFiles mount at /).
# Honored env vars:
# OPENUTM_CONFIG_PATH, FLIGHT_BLENDER_URL, LOG_LEVEL
ENTRYPOINT ["python", "-m", "uvicorn", "openutm_verification.server.main:app"]
CMD ["--host", "0.0.0.0", "--port", "8989"]
74 changes: 0 additions & 74 deletions Dockerfile.dev

This file was deleted.

Loading
Loading