working on runtime executions
This commit is contained in:
@@ -1,87 +1,24 @@
|
||||
# Optimized Multi-stage Dockerfile for Attune Rust services
|
||||
# This Dockerfile minimizes layer invalidation by selectively copying only required crates
|
||||
# Multi-stage Dockerfile for Attune Rust services (api, executor, sensor, notifier)
|
||||
#
|
||||
# Key optimizations:
|
||||
# 1. Copy only Cargo.toml files first to cache dependency downloads
|
||||
# 2. Build dummy binaries to cache compiled dependencies
|
||||
# 3. Copy only the specific crate being built (plus common)
|
||||
# 4. Use BuildKit cache mounts for cargo registry and build artifacts
|
||||
# Simple and robust: build the entire workspace, then copy the target binary.
|
||||
# No dummy sources, no selective crate copying, no fragile hacks.
|
||||
#
|
||||
# Usage: DOCKER_BUILDKIT=1 docker build --build-arg SERVICE=api -f docker/Dockerfile.optimized -t attune-api .
|
||||
# Usage:
|
||||
# DOCKER_BUILDKIT=1 docker build --build-arg SERVICE=api -f docker/Dockerfile.optimized -t attune-api .
|
||||
# DOCKER_BUILDKIT=1 docker build --build-arg SERVICE=executor -f docker/Dockerfile.optimized -t attune-executor .
|
||||
# DOCKER_BUILDKIT=1 docker build --build-arg SERVICE=sensor -f docker/Dockerfile.optimized -t attune-sensor .
|
||||
# DOCKER_BUILDKIT=1 docker build --build-arg SERVICE=notifier -f docker/Dockerfile.optimized -t attune-notifier .
|
||||
#
|
||||
# Build time comparison (after common crate changes):
|
||||
# - Old: ~5 minutes (rebuilds all dependencies)
|
||||
# - New: ~30 seconds (only recompiles changed code)
|
||||
#
|
||||
# Note: This Dockerfile does NOT copy packs into the image.
|
||||
# Packs are mounted as volumes at runtime from the packs_data volume.
|
||||
# The init-packs service in docker-compose.yaml handles pack initialization.
|
||||
# Note: Packs are NOT copied into the image — they are mounted as volumes at runtime.
|
||||
|
||||
ARG RUST_VERSION=1.92
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
|
||||
# ============================================================================
|
||||
# Stage 1: Planner - Extract dependency information
|
||||
# ============================================================================
|
||||
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS planner
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy only Cargo.toml and Cargo.lock to understand dependencies
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
# Copy all crate manifests (but not source code)
|
||||
# This allows cargo to resolve the workspace without needing source
|
||||
COPY crates/common/Cargo.toml ./crates/common/Cargo.toml
|
||||
COPY crates/api/Cargo.toml ./crates/api/Cargo.toml
|
||||
COPY crates/executor/Cargo.toml ./crates/executor/Cargo.toml
|
||||
COPY crates/sensor/Cargo.toml ./crates/sensor/Cargo.toml
|
||||
COPY crates/core-timer-sensor/Cargo.toml ./crates/core-timer-sensor/Cargo.toml
|
||||
COPY crates/worker/Cargo.toml ./crates/worker/Cargo.toml
|
||||
COPY crates/notifier/Cargo.toml ./crates/notifier/Cargo.toml
|
||||
COPY crates/cli/Cargo.toml ./crates/cli/Cargo.toml
|
||||
|
||||
# Create dummy lib.rs and main.rs files for all crates
|
||||
# This allows us to build dependencies without the actual source code
|
||||
RUN mkdir -p crates/common/src && echo "fn main() {}" > crates/common/src/lib.rs
|
||||
RUN mkdir -p crates/api/src && echo "fn main() {}" > crates/api/src/main.rs
|
||||
RUN mkdir -p crates/executor/src && echo "fn main() {}" > crates/executor/src/main.rs
|
||||
RUN mkdir -p crates/executor/benches && echo "fn main() {}" > crates/executor/benches/context_clone.rs
|
||||
RUN mkdir -p crates/sensor/src && echo "fn main() {}" > crates/sensor/src/main.rs
|
||||
RUN mkdir -p crates/core-timer-sensor/src && echo "fn main() {}" > crates/core-timer-sensor/src/main.rs
|
||||
RUN mkdir -p crates/worker/src && echo "fn main() {}" > crates/worker/src/main.rs
|
||||
RUN mkdir -p crates/notifier/src && echo "fn main() {}" > crates/notifier/src/main.rs
|
||||
RUN mkdir -p crates/cli/src && echo "fn main() {}" > crates/cli/src/main.rs
|
||||
|
||||
# Copy SQLx metadata for compile-time query checking
|
||||
COPY .sqlx/ ./.sqlx/
|
||||
|
||||
# Build argument to specify which service to build
|
||||
ARG SERVICE=api
|
||||
|
||||
# Build dependencies only (with dummy source)
|
||||
# This layer is only invalidated when Cargo.toml or Cargo.lock changes
|
||||
# BuildKit cache mounts persist cargo registry and git cache
|
||||
# - registry/git use sharing=shared (cargo handles concurrent access safely)
|
||||
# - target uses service-specific cache ID to avoid conflicts between services
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
--mount=type=cache,target=/build/target,id=target-planner-${SERVICE} \
|
||||
cargo build --release --bin attune-${SERVICE} || true
|
||||
|
||||
# ============================================================================
|
||||
# Stage 2: Builder - Compile the actual service
|
||||
# Stage 1: Builder - Compile the entire workspace
|
||||
# ============================================================================
|
||||
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
@@ -90,10 +27,9 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy workspace configuration
|
||||
# Copy dependency metadata first so `cargo fetch` layer is cached
|
||||
# when only source code changes (Cargo.toml/Cargo.lock stay the same)
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
# Copy all crate manifests (required for workspace resolution)
|
||||
COPY crates/common/Cargo.toml ./crates/common/Cargo.toml
|
||||
COPY crates/api/Cargo.toml ./crates/api/Cargo.toml
|
||||
COPY crates/executor/Cargo.toml ./crates/executor/Cargo.toml
|
||||
@@ -103,106 +39,87 @@ COPY crates/worker/Cargo.toml ./crates/worker/Cargo.toml
|
||||
COPY crates/notifier/Cargo.toml ./crates/notifier/Cargo.toml
|
||||
COPY crates/cli/Cargo.toml ./crates/cli/Cargo.toml
|
||||
|
||||
# Create dummy source files for workspace members that won't be built
|
||||
# This satisfies workspace resolution without copying full source
|
||||
RUN mkdir -p crates/api/src && echo "fn main() {}" > crates/api/src/main.rs
|
||||
RUN mkdir -p crates/executor/src && echo "fn main() {}" > crates/executor/src/main.rs
|
||||
RUN mkdir -p crates/executor/benches && echo "fn main() {}" > crates/executor/benches/context_clone.rs
|
||||
RUN mkdir -p crates/sensor/src && echo "fn main() {}" > crates/sensor/src/main.rs
|
||||
RUN mkdir -p crates/core-timer-sensor/src && echo "fn main() {}" > crates/core-timer-sensor/src/main.rs
|
||||
RUN mkdir -p crates/worker/src && echo "fn main() {}" > crates/worker/src/main.rs
|
||||
RUN mkdir -p crates/notifier/src && echo "fn main() {}" > crates/notifier/src/main.rs
|
||||
RUN mkdir -p crates/cli/src && echo "fn main() {}" > crates/cli/src/main.rs
|
||||
# Create minimal stub sources so cargo can resolve the workspace and fetch deps.
|
||||
# These are ONLY used for `cargo fetch` — never compiled.
|
||||
RUN mkdir -p crates/common/src && echo "" > crates/common/src/lib.rs && \
|
||||
mkdir -p crates/api/src && echo "fn main(){}" > crates/api/src/main.rs && \
|
||||
mkdir -p crates/executor/src && echo "fn main(){}" > crates/executor/src/main.rs && \
|
||||
mkdir -p crates/executor/benches && echo "fn main(){}" > crates/executor/benches/context_clone.rs && \
|
||||
mkdir -p crates/sensor/src && echo "fn main(){}" > crates/sensor/src/main.rs && \
|
||||
mkdir -p crates/core-timer-sensor/src && echo "fn main(){}" > crates/core-timer-sensor/src/main.rs && \
|
||||
mkdir -p crates/worker/src && echo "fn main(){}" > crates/worker/src/main.rs && \
|
||||
mkdir -p crates/notifier/src && echo "fn main(){}" > crates/notifier/src/main.rs && \
|
||||
mkdir -p crates/cli/src && echo "fn main(){}" > crates/cli/src/main.rs
|
||||
|
||||
# Copy SQLx metadata
|
||||
# Download all dependencies (cached unless Cargo.toml/Cargo.lock change)
|
||||
# registry/git use sharing=shared — cargo handles concurrent reads safely
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
cargo fetch
|
||||
|
||||
# Now copy the real source code, SQLx metadata, and migrations
|
||||
COPY .sqlx/ ./.sqlx/
|
||||
|
||||
# Copy migrations (required for some services)
|
||||
COPY migrations/ ./migrations/
|
||||
COPY crates/ ./crates/
|
||||
|
||||
# Copy the common crate (almost all services depend on this)
|
||||
COPY crates/common/ ./crates/common/
|
||||
|
||||
# Build the specified service
|
||||
# The cargo registry and git cache are pre-populated from the planner stage
|
||||
# Only the actual compilation happens here
|
||||
# - registry/git use sharing=shared (concurrent builds of different services are safe)
|
||||
# - target uses service-specific cache ID (each service compiles different crates)
|
||||
# Build the entire workspace in release mode.
|
||||
# All binaries are compiled together, sharing dependency compilation.
|
||||
# target cache uses sharing=locked so concurrent service builds serialize
|
||||
# writes to the shared compilation cache instead of corrupting it.
|
||||
#
|
||||
# IMPORTANT: ARG SERVICE is declared AFTER this RUN so that changing the
|
||||
# SERVICE value does not invalidate the cached build layer. The first
|
||||
# service to build compiles the full workspace; subsequent services get
|
||||
# a cache hit here and skip straight to the cp below.
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
--mount=type=cache,target=/build/target,sharing=locked \
|
||||
cargo build --release --lib -p attune-common
|
||||
cargo build --release --workspace --bins -j 4
|
||||
|
||||
|
||||
# Build argument to specify which service to build
|
||||
# Extract the requested service binary from the target cache.
|
||||
# This is the only layer that varies per SERVICE value.
|
||||
ARG SERVICE=api
|
||||
|
||||
# Copy only the source for the service being built
|
||||
# This is the key optimization: changes to other crates won't invalidate this layer
|
||||
COPY crates/${SERVICE}/ ./crates/${SERVICE}/
|
||||
|
||||
# Build the specified service
|
||||
# The cargo registry and git cache are pre-populated from the planner stage
|
||||
# Only the actual compilation happens here
|
||||
# - registry/git use sharing=shared (concurrent builds of different services are safe)
|
||||
# - target uses service-specific cache ID (each service compiles different crates)
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
--mount=type=cache,target=/build/target,sharing=shared \
|
||||
cargo build --release --bin attune-${SERVICE} && \
|
||||
RUN --mount=type=cache,target=/build/target,sharing=locked \
|
||||
cp /build/target/release/attune-${SERVICE} /build/attune-service-binary
|
||||
|
||||
# ============================================================================
|
||||
# Stage 3: Runtime - Create minimal runtime image
|
||||
# Stage 2: Runtime - Minimal image with just the service binary
|
||||
# ============================================================================
|
||||
FROM debian:${DEBIAN_VERSION}-slim AS runtime
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
libssl3 \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user and directories
|
||||
# Note: /opt/attune/packs is mounted as a volume at runtime, not copied in
|
||||
# /opt/attune/packs is mounted as a volume at runtime, not copied in
|
||||
RUN useradd -m -u 1000 attune && \
|
||||
mkdir -p /opt/attune/packs /opt/attune/logs && \
|
||||
mkdir -p /opt/attune/packs /opt/attune/logs /opt/attune/runtime_envs && \
|
||||
chown -R attune:attune /opt/attune
|
||||
|
||||
WORKDIR /opt/attune
|
||||
|
||||
# Copy the service binary from builder
|
||||
# Copy the service binary from builder using a fixed path (no variable in COPY source)
|
||||
# This avoids the circular dependency Docker hits when using ARG in --from paths
|
||||
COPY --from=builder /build/attune-service-binary /usr/local/bin/attune-service
|
||||
|
||||
# Copy configuration file for Docker Compose development
|
||||
# In production, mount config files as a volume instead of baking them into the image
|
||||
# Copy configuration and migrations
|
||||
COPY config.docker.yaml ./config.yaml
|
||||
|
||||
# Copy migrations for services that need them
|
||||
COPY migrations/ ./migrations/
|
||||
|
||||
# Note: Packs are NOT copied into the image
|
||||
# They are mounted as a volume at runtime from the packs_data volume
|
||||
# The init-packs service populates the packs_data volume from ./packs directory
|
||||
# Pack binaries (like attune-core-timer-sensor) are also in the mounted volume
|
||||
|
||||
# Set ownership (packs will be mounted at runtime)
|
||||
RUN chown -R attune:attune /opt/attune
|
||||
|
||||
# Switch to non-root user
|
||||
USER attune
|
||||
|
||||
# Environment variables (can be overridden at runtime)
|
||||
ENV RUST_LOG=info
|
||||
ENV ATTUNE_CONFIG=/opt/attune/config.yaml
|
||||
|
||||
# Health check (will be overridden per service in docker-compose)
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
# Expose default port (override per service)
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the service
|
||||
CMD ["/usr/local/bin/attune-service"]
|
||||
|
||||
Reference in New Issue
Block a user