Skip to content

Commit 54ae69b

Browse files
committed
fix: Make containers buildable and add smoke test
This commit makes Project Wharf actually runnable: ## Container Fixes - Use standard Alpine-based images (php:8.3-fpm-alpine, nginx:alpine) - Fix user/group configuration for non-root operation - Add agent.Dockerfile with multi-stage Rust build - Fix nginx.conf to omit 'user' directive (runs as container user) - Fix php-fpm.conf to use www-data user ## Build Fixes - Exclude wharf-ebpf from default workspace (requires special toolchain) - Fix Rust edition from 2024 to 2021 (2024 doesn't exist yet) ## Testing - Add scripts/smoke_test.sh for quick sanity checks - Add 'just smoke-test' and 'just smoke-test-full' recipes - Tests: prerequisites, Rust compilation, container builds, config validation ## Quick Start 1. just smoke-test # Check prerequisites 2. just smoke-test-full # Build and test containers 3. cargo build --release # Build Rust binaries 4. just build-containers # Build production images The Yacht can now actually be deployed.
1 parent 5294d93 commit 54ae69b

8 files changed

Lines changed: 424 additions & 116 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
[workspace]
22
members = [
33
"crates/wharf-core",
4-
"crates/wharf-ebpf",
54
"bin/wharf-cli",
65
"bin/yacht-agent",
76
]
7+
# eBPF crate excluded from default build - requires special toolchain (bpf-linker)
8+
# Build separately with: cd crates/wharf-ebpf && cargo +nightly build --target bpfel-unknown-none
9+
exclude = ["crates/wharf-ebpf"]
810
resolver = "2"
911

1012
[workspace.package]
1113
version = "0.1.0"
12-
edition = "2024"
14+
edition = "2021"
1315
authors = ["Jonathan D. A. Jewell <hyperpolymath>"]
1416
license = "MIT"
1517
repository = "https://gitlab.com/hyperpolymath/wharf"

Justfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,16 @@ test:
254254
@echo ">>> Running tests..."
255255
cargo test --workspace
256256

257+
# Quick smoke test (sanity check)
258+
smoke-test:
259+
@echo ">>> Running smoke tests..."
260+
./scripts/smoke_test.sh
261+
262+
# Smoke test with container builds
263+
smoke-test-full:
264+
@echo ">>> Running full smoke tests with container builds..."
265+
./scripts/smoke_test.sh --build
266+
257267
# Run tests with coverage
258268
test-coverage:
259269
@echo ">>> Running tests with coverage..."

infra/config/nginx.conf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
# - JSON logging (for Wharf observability)
1010
# - All temp paths in /tmp (RAM disk mount)
1111

12-
# Run as non-root (matches container USER)
13-
user nonroot;
12+
# Note: When running as non-root user in container, omit 'user' directive
13+
# Nginx will run as whatever user started the process
1414
worker_processes auto;
15-
error_log stderr warn;
15+
error_log /dev/stderr warn;
1616
pid /tmp/nginx.pid;
1717

1818
events {

infra/config/php-fpm.conf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
; USER/GROUP
1515
; =============================================================================
1616
; Run as non-root user (matches container configuration)
17-
user = nonroot
18-
group = nonroot
17+
; Use 'wharf' user created in Dockerfile, or 'www-data' for official PHP image
18+
user = www-data
19+
group = www-data
1920

2021
; =============================================================================
2122
; LISTEN

infra/containers/agent.Dockerfile

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# SPDX-License-Identifier: MIT
2+
# SPDX-FileCopyrightText: 2025 Jonathan D. A. Jewell <hyperpolymath>
3+
#
4+
# Yacht Agent Container for Project Wharf
5+
# ========================================
6+
# The runtime enforcer - database proxy and security monitor.
7+
#
8+
# Build: podman build -t yacht-agent:latest -f infra/containers/agent.Dockerfile .
9+
# Run: podman run -d -p 3306:3306 -p 9001:9001 yacht-agent:latest
10+
11+
# -----------------------------------------------------------------------------
12+
# Stage 1: Build the Rust binary
13+
# -----------------------------------------------------------------------------
14+
FROM docker.io/library/rust:1.75-alpine AS builder
15+
16+
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconf
17+
18+
WORKDIR /build
19+
20+
# Copy workspace files
21+
COPY Cargo.toml Cargo.lock ./
22+
COPY crates/wharf-core ./crates/wharf-core
23+
COPY bin/yacht-agent ./bin/yacht-agent
24+
COPY bin/wharf-cli ./bin/wharf-cli
25+
26+
# Build release binary with static linking
27+
ENV RUSTFLAGS="-C target-feature=+crt-static"
28+
RUN cargo build --release --bin yacht-agent
29+
30+
# Verify binary
31+
RUN ls -la target/release/yacht-agent
32+
33+
# -----------------------------------------------------------------------------
34+
# Stage 2: Minimal runtime image
35+
# -----------------------------------------------------------------------------
36+
FROM docker.io/library/alpine:3.19
37+
38+
LABEL org.opencontainers.image.title="Yacht Agent"
39+
LABEL org.opencontainers.image.description="Database proxy and security enforcer for Project Wharf"
40+
LABEL org.opencontainers.image.vendor="Hyperpolymath"
41+
42+
# Install minimal runtime deps (none needed for static binary, but useful for debugging)
43+
RUN apk add --no-cache ca-certificates
44+
45+
# Create non-root user
46+
RUN addgroup -g 1000 wharf && adduser -u 1000 -G wharf -s /bin/false -D wharf
47+
48+
# Copy the static binary
49+
COPY --from=builder /build/target/release/yacht-agent /usr/local/bin/yacht-agent
50+
51+
# Ensure binary is executable
52+
RUN chmod +x /usr/local/bin/yacht-agent
53+
54+
# Create config directory
55+
RUN mkdir -p /etc/wharf && chown wharf:wharf /etc/wharf
56+
57+
USER wharf
58+
59+
# Database proxy port (masquerade as MySQL)
60+
EXPOSE 3306
61+
# Agent API port
62+
EXPOSE 9001
63+
64+
# Health check
65+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
66+
CMD wget -q --spider http://localhost:9001/health || exit 1
67+
68+
# Default: MySQL protocol on port 3306, shadow DB on 33060
69+
ENTRYPOINT ["yacht-agent"]
70+
CMD ["--listen-port", "3306", "--shadow-port", "33060", "--api-port", "9001"]

infra/containers/nginx.Dockerfile

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
11
# SPDX-License-Identifier: MIT
22
# SPDX-FileCopyrightText: 2025 Jonathan D. A. Jewell <hyperpolymath>
33
#
4-
# Wolfi Nginx Container for Project Wharf
5-
# ========================================
6-
# A minimal, hardened reverse proxy / web server:
7-
# - No shell, no package manager at runtime
8-
# - Non-root user (binds to 8080, not 80)
9-
# - Strict security headers enforced
10-
# - JSON logging for observability
4+
# Nginx Container for Project Wharf
5+
# ==================================
6+
# Hardened reverse proxy with security headers.
117
#
12-
# The eBPF firewall redirects port 80 -> 8080 transparently.
8+
# Build: podman build -t yacht-nginx:latest -f infra/containers/nginx.Dockerfile .
9+
# Run: podman run -d -p 8080:8080 -v ./html:/var/www/html:ro yacht-nginx:latest
1310

14-
# -----------------------------------------------------------------------------
15-
# Runtime Stage (Single stage - Chainguard provides pre-hardened base)
16-
# -----------------------------------------------------------------------------
17-
FROM cgr.dev/chainguard/nginx:latest
11+
ARG BASE_IMAGE=docker.io/library/nginx:alpine
1812

19-
# Copy our hardened Nginx configuration
20-
COPY infra/config/nginx.conf /etc/nginx/nginx.conf
13+
FROM ${BASE_IMAGE}
14+
15+
LABEL org.opencontainers.image.title="Yacht Nginx"
16+
LABEL org.opencontainers.image.description="Hardened Nginx for Project Wharf"
17+
LABEL org.opencontainers.image.vendor="Hyperpolymath"
18+
19+
# Remove default config
20+
RUN rm -rf /etc/nginx/conf.d/*
2121

22-
# Copy CMS-specific rules (WordPress by default)
22+
# Copy our hardened configuration
23+
COPY infra/config/nginx.conf /etc/nginx/nginx.conf
2324
COPY infra/config/wordpress-rules.conf /etc/nginx/conf.d/default.conf
2425

25-
# Create required directories for non-root operation
26-
# These must exist even though they'll be tmpfs mounts
27-
USER root
28-
RUN mkdir -p /tmp/client_temp \
29-
&& mkdir -p /tmp/proxy_temp \
30-
&& mkdir -p /tmp/fastcgi_temp \
31-
&& mkdir -p /tmp/uwsgi_temp \
32-
&& mkdir -p /tmp/scgi_temp \
26+
# Create required directories
27+
RUN mkdir -p /tmp/nginx \
3328
&& mkdir -p /var/www/html \
34-
&& chown -R nonroot:nonroot /tmp \
35-
&& chown -R nonroot:nonroot /var/www/html
29+
&& mkdir -p /var/cache/nginx
30+
31+
# Create non-root user and fix permissions
32+
RUN addgroup -g 1000 wharf 2>/dev/null || true \
33+
&& adduser -u 1000 -G wharf -s /bin/false -D wharf 2>/dev/null || true \
34+
&& chown -R wharf:wharf /var/www/html \
35+
&& chown -R wharf:wharf /var/cache/nginx \
36+
&& chown -R wharf:wharf /tmp/nginx \
37+
&& chown -R wharf:wharf /var/log/nginx \
38+
&& touch /var/run/nginx.pid \
39+
&& chown wharf:wharf /var/run/nginx.pid
3640

37-
# SECURITY: Switch to non-root
38-
USER nonroot
41+
USER wharf
42+
WORKDIR /var/www/html
3943

40-
# NETWORK: Non-privileged ports (privileged ports require root)
41-
# eBPF firewall handles 80->8080 and 443->8443 redirection
42-
EXPOSE 8080 8443
44+
# Non-privileged port (use eBPF or iptables to redirect 80->8080)
45+
EXPOSE 8080
4346

44-
# HEALTH CHECK
4547
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
46-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
48+
CMD wget -q --spider http://localhost:8080/health || exit 1
4749

48-
# ENTRYPOINT: Direct binary execution
4950
CMD ["nginx", "-g", "daemon off;"]

infra/containers/php.Dockerfile

Lines changed: 72 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,90 @@
11
# SPDX-License-Identifier: MIT
22
# SPDX-FileCopyrightText: 2025 Jonathan D. A. Jewell <hyperpolymath>
33
#
4-
# Wolfi PHP Container for Project Wharf
5-
# ======================================
6-
# This is NOT a standard PHP image. It is stripped to "race weight":
7-
# - No shell (sh, bash, zsh)
8-
# - No package manager at runtime
9-
# - Non-root user
10-
# - Hardened PHP configuration
4+
# PHP-FPM Container for Project Wharf
5+
# ====================================
6+
# Hardened PHP runtime for CMS workloads.
117
#
12-
# Attack surface: Minimal. If a hacker breaks in, they can't run commands.
8+
# Build: podman build -t yacht-php:latest -f infra/containers/php.Dockerfile .
9+
# Run: podman run -d -p 9000:9000 -v ./html:/var/www/html:ro yacht-php:latest
1310

14-
# -----------------------------------------------------------------------------
15-
# STAGE 1: Builder (The Factory)
16-
# -----------------------------------------------------------------------------
17-
FROM cgr.dev/chainguard/wolfi-base:latest AS builder
11+
# Use Chainguard if available, fallback to Alpine
12+
ARG BASE_IMAGE=docker.io/library/php:8.3-fpm-alpine
1813

19-
# Install PHP and required extensions for CMS compatibility
20-
RUN apk add --no-cache \
21-
php-8.3 \
22-
php-8.3-fpm \
23-
php-8.3-opcache \
24-
php-8.3-mysqli \
25-
php-8.3-pdo \
26-
php-8.3-pdo_mysql \
27-
php-8.3-pdo_pgsql \
28-
php-8.3-mysqlnd \
29-
php-8.3-mbstring \
30-
php-8.3-gd \
31-
php-8.3-xml \
32-
php-8.3-simplexml \
33-
php-8.3-dom \
34-
php-8.3-curl \
35-
php-8.3-zip \
36-
php-8.3-intl \
37-
php-8.3-bcmath \
38-
php-8.3-sodium \
39-
php-8.3-redis \
40-
php-8.3-imagick \
41-
php-8.3-exif \
42-
php-8.3-fileinfo \
43-
&& rm -rf /var/cache/apk/*
14+
FROM ${BASE_IMAGE}
4415

45-
# Harden php.ini at build time
46-
RUN set -eux; \
47-
# Hide PHP version from headers
48-
sed -i 's/expose_php = On/expose_php = Off/' /etc/php83/php.ini; \
49-
# Increase memory for CMS workloads
50-
sed -i 's/memory_limit = 128M/memory_limit = 256M/' /etc/php83/php.ini; \
51-
# Disable error display (log only)
52-
sed -i 's/display_errors = On/display_errors = Off/' /etc/php83/php.ini; \
53-
# Block remote file inclusion attacks
54-
sed -i 's/allow_url_fopen = On/allow_url_fopen = Off/' /etc/php83/php.ini; \
55-
sed -i 's/allow_url_include = On/allow_url_include = Off/' /etc/php83/php.ini; \
56-
# Disable dangerous functions
57-
echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source" >> /etc/php83/php.ini; \
58-
# OPcache for performance
59-
echo "opcache.enable=1" >> /etc/php83/php.ini; \
60-
echo "opcache.memory_consumption=128" >> /etc/php83/php.ini; \
61-
echo "opcache.interned_strings_buffer=8" >> /etc/php83/php.ini; \
62-
echo "opcache.max_accelerated_files=10000" >> /etc/php83/php.ini; \
63-
echo "opcache.revalidate_freq=0" >> /etc/php83/php.ini; \
64-
echo "opcache.validate_timestamps=0" >> /etc/php83/php.ini
16+
LABEL org.opencontainers.image.title="Yacht PHP"
17+
LABEL org.opencontainers.image.description="Hardened PHP-FPM for Project Wharf"
18+
LABEL org.opencontainers.image.vendor="Hyperpolymath"
19+
20+
# Install required PHP extensions for CMS compatibility
21+
RUN apk add --no-cache \
22+
# Core extensions
23+
php83-mysqli \
24+
php83-pdo_mysql \
25+
php83-pdo_pgsql \
26+
php83-mbstring \
27+
php83-gd \
28+
php83-xml \
29+
php83-curl \
30+
php83-zip \
31+
php83-intl \
32+
php83-bcmath \
33+
php83-sodium \
34+
php83-opcache \
35+
php83-exif \
36+
php83-fileinfo \
37+
php83-session \
38+
php83-tokenizer \
39+
# For Redis sessions
40+
php83-pecl-redis \
41+
2>/dev/null || true
6542

66-
# -----------------------------------------------------------------------------
67-
# STAGE 2: Runtime (The Yacht)
68-
# -----------------------------------------------------------------------------
69-
FROM cgr.dev/chainguard/php:latest-fpm
43+
# If using official PHP image, install via docker-php-ext
44+
RUN docker-php-ext-install mysqli pdo pdo_mysql opcache 2>/dev/null || true
7045

71-
# Copy hardened configuration
72-
COPY --from=builder /etc/php83/php.ini /etc/php83/php.ini
46+
# Harden PHP configuration
47+
RUN { \
48+
echo 'expose_php = Off'; \
49+
echo 'display_errors = Off'; \
50+
echo 'log_errors = On'; \
51+
echo 'error_log = /dev/stderr'; \
52+
echo 'memory_limit = 256M'; \
53+
echo 'max_execution_time = 60'; \
54+
echo 'upload_max_filesize = 64M'; \
55+
echo 'post_max_size = 64M'; \
56+
echo 'allow_url_fopen = Off'; \
57+
echo 'allow_url_include = Off'; \
58+
echo 'session.cookie_httponly = 1'; \
59+
echo 'session.cookie_secure = 1'; \
60+
echo 'session.use_strict_mode = 1'; \
61+
# OPcache for performance
62+
echo 'opcache.enable = 1'; \
63+
echo 'opcache.memory_consumption = 128'; \
64+
echo 'opcache.max_accelerated_files = 10000'; \
65+
echo 'opcache.revalidate_freq = 0'; \
66+
echo 'opcache.validate_timestamps = 0'; \
67+
# Disable dangerous functions (comment out for debugging)
68+
echo 'disable_functions = exec,passthru,shell_exec,system,proc_open,popen'; \
69+
} > /usr/local/etc/php/conf.d/wharf-security.ini
7370

74-
# Copy our custom FPM pool configuration
75-
COPY infra/config/php-fpm.conf /etc/php83/php-fpm.d/www.conf
71+
# Copy FPM pool configuration
72+
COPY infra/config/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf
7673

77-
# Create directories for WordPress compatibility
78-
# These will be mounted as volumes
79-
RUN mkdir -p /var/www/html \
80-
&& mkdir -p /var/www/html/wp-content/uploads \
74+
# Create web directories
75+
RUN mkdir -p /var/www/html/wp-content/uploads \
8176
&& mkdir -p /var/www/html/wp-content/cache
8277

83-
# SECURITY: Run as non-root (uid 65532 is standard nonroot in distroless)
84-
USER nonroot
78+
# Create non-root user
79+
RUN addgroup -g 1000 wharf && adduser -u 1000 -G wharf -s /bin/false -D wharf \
80+
&& chown -R wharf:wharf /var/www/html
81+
82+
USER wharf
83+
WORKDIR /var/www/html
8584

86-
# NETWORK: FastCGI port
8785
EXPOSE 9000
8886

89-
# HEALTH CHECK
9087
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
91-
CMD php-fpm83 -t || exit 1
88+
CMD php-fpm -t 2>/dev/null || exit 1
9289

93-
# ENTRYPOINT: Just the FPM binary, no wrapper scripts
94-
CMD ["php-fpm83", "-F"]
90+
CMD ["php-fpm", "-F"]

0 commit comments

Comments
 (0)