From 0103d2939d5686d60cef4aceff0fa3943cb609ad Mon Sep 17 00:00:00 2001 From: danielvici123 <94993276+danielvici@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:13:39 +0200 Subject: [PATCH] feat: Add Dockerization support --- .dockerignore | 31 +++++++++++++++++++++++++++++++ Dockerfile | 44 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 11 +++++++++++ src/config.rs | 18 +++++++++++------- src/main.rs | 9 ++++++++- 6 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e546417 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Rust +target/ +**/*.rs.bk + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore + +# Config and local data +*.json +!config.json.example +!response.json.example +status.json + +# IDEs and Editors +.vscode/ +.idea/ +*.swp +*.swo + +# CI/CD +.gitea/ +.github/ + +# OS files +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0c9323c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# Build stage +FROM rust:alpine AS builder + +# Install build dependencies +RUN apk add --no-cache musl-dev + +WORKDIR /usr/src/dhl + +# 1. Copy only the dependency manifests +COPY Cargo.toml Cargo.lock ./ + +# 2. Create a dummy source file to build dependencies +RUN mkdir src && echo "fn main() {}" > src/main.rs && \ + cargo build --release && \ + rm -rf src + +# 3. Now copy the real source code +COPY src ./src + +# 4. Build the actual application +# We touch the main file to ensure cargo rebuilds it +RUN touch src/main.rs && cargo build --release + +# Final stage +FROM alpine:latest + +# Set environment variable to signal the app it's running in Docker +ENV DOCKER_CONTAINER=true + +# Install runtime dependencies +RUN apk add --no-cache ca-certificates + +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /usr/src/dhl/target/release/dhl /usr/local/bin/dhl + +# Volume for configuration and response files +VOLUME ["/app"] + +# Default port +EXPOSE 3000 + +CMD ["dhl"] diff --git a/README.md b/README.md index 9b47ba3..32498f3 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,52 @@ cargo run cargo build --release ``` +### Using with Docker + +You can run DHL as a Docker container. The application automatically generates a default `config.json` and `response.json` if they are missing in the work directory. + +#### Build the Image + +```bash +docker build -t dhl . +``` + +#### Run with Docker CLI + +To persist your configuration and response files, mount a local directory to `/app` in the container. The application will create default files in that directory if it is empty. + +```bash +# Create a folder for your mock data +mkdir mock-data + +# Run the container and mount the folder +docker run -p 3000:3000 -v $(pwd)/mock-data:/app dhl +``` + +#### Run with Docker Compose + +An example `docker-compose.yml` is provided in the root directory: + +```yaml +version: '3.8' + +services: + dhl: + build: . + ports: + - "3000:3000" + volumes: + - ./mock-data:/app + restart: unless-stopped +``` + +To start: +```bash +docker-compose up -d +``` + +**Note:** The default host is set to `0.0.0.0` for Docker compatibility. If you are using a custom `config.json`, ensure the `host` is set to `0.0.0.0`. + ### Release Assets When downloading a release, you will find: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0d9cecd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + dhl: + build: . + image: dhl:latest + container_name: dhl + ports: + - "3000:3000" + volumes: + # Mount a local folder to /app to manage config.json and response files + - ./mock-data:/app + restart: unless-stopped diff --git a/src/config.rs b/src/config.rs index 638bded..b9401ad 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,28 +1,28 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::fs; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct TlsConfig { pub enabled: bool, pub cert_path: PathBuf, pub key_path: PathBuf, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct MaskingConfig { pub enabled: bool, pub headers: Vec, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct RouteConfig { pub path: String, pub response_file: PathBuf, pub status_code: u16, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Config { pub host: String, pub port: u16, @@ -41,7 +41,11 @@ impl Config { }) } Err(_) => { - Self::default() + let default_config = Self::default(); + if let Ok(json) = serde_json::to_string_pretty(&default_config) { + let _ = fs::write("config.json", json); + } + default_config } } } @@ -50,7 +54,7 @@ impl Config { impl Default for Config { fn default() -> Self { Self { - host: "127.0.0.1".to_string(), + host: "0.0.0.0".to_string(), port: 3000, routes: vec![RouteConfig { path: "/".to_string(), diff --git a/src/main.rs b/src/main.rs index b6681a6..d751af6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,14 @@ async fn main() -> Result<(), Box> { .layer(axum_middleware::from_fn_with_state(state.clone(), logging_middleware)) .with_state(state); - let addr: SocketAddr = format!("{}:{}", config.host, config.port).parse()?; + let mut host = config.host.clone(); + + // In Docker, we usually want to bind to 0.0.0.0 to be accessible + if std::env::var("DOCKER_CONTAINER").is_ok() { + host = "0.0.0.0".to_string(); + } + + let addr: SocketAddr = format!("{}:{}", host, config.port).parse()?; // 4. Start server if config.tls.enabled {