diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..28ed30f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +# Git +.git +.gitignore + +# Build artifacts +build/ +minim + +# Documentation +*.md +docs/ +LICENSE + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo + +# Claude Code +.claude/ + +# Local config +mise.toml + +# Docker Compose files (not needed in app image) +compose.yml +.env +.env.example +proxy/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b937210 --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# Minimalytics Docker Compose Configuration +# Copy this file to .env and customize the values + +# ============================================================================= +# Port Configuration +# ============================================================================= + +# Internal port for minimalytics (not exposed to host by default) +MINIM_PORT=3333 + +# External port for the authenticated proxy (exposed to host) +PROXY_PORT=3334 + +# ============================================================================= +# Authentication +# ============================================================================= + +# Username for dashboard access +AUTH_USER=admin + +# Password for dashboard access (change this!) +AUTH_PASS=changeme + +# ============================================================================= +# Optional: Domain Configuration +# ============================================================================= + +# Your domain name (used for logging/identification, not for TLS) +# TLS termination is handled by your production reverse proxy +DOMAIN=localhost diff --git a/.gitignore b/.gitignore index b5215a6..3165f27 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ templates/* .DS_Store build/* + +# Environment file with credentials +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..11447c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# Build stage +FROM golang:1.23-alpine AS builder + +# Install build dependencies for CGO (required by SQLite) +RUN apk add --no-cache gcc musl-dev + +WORKDIR /app + +# Copy go mod files first for better layer caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build with CGO enabled for SQLite support +RUN CGO_ENABLED=1 go build -ldflags="-s -w" -o minim . + +# Runtime stage +FROM alpine:3.20 + +# Install runtime dependencies for SQLite +RUN apk add --no-cache ca-certificates + +WORKDIR /app + +# Copy binary from builder +COPY --from=builder /app/minim . + +# Copy static files for the web UI +COPY --from=builder /app/static ./static + +# Create data directory +RUN mkdir -p /root/.minim + +# Expose default port +EXPOSE 3333 + +# Run the server directly (execserver runs in foreground) +CMD ["./minim", "execserver"] diff --git a/README.md b/README.md index bbc2f05..ebf82bb 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,45 @@ Minimalytics is a **standalone minimalist analytics tool** built on SQLite. Desi sudo mv build/minim /usr/local/bin/ ``` +### Docker + +1. Build the image: + ```bash + docker build -t minimalytics . + ``` + +2. Run the container: + ```bash + docker run -p 3333:3333 -v minim-data:/root/.minim minimalytics + ``` + + The volume mount persists the SQLite database across container restarts. + +### Docker Compose (with authentication) + +Docker Compose runs minimalytics with an HAProxy reverse proxy that adds basic authentication to the dashboard while leaving the event API open. + +1. Copy the example environment file and configure: + ```bash + cp .env.example .env + ``` + +2. Edit `.env` with your credentials: + ``` + AUTH_USER=admin + AUTH_PASS=your-secure-password + PROXY_PORT=3334 + ``` + +3. Start the services: + ```bash + docker compose up -d + ``` + +4. Access the services: + - **Dashboard** (requires auth): `http://localhost:3334` + - **Event API** (no auth): `http://localhost:3334/api/event/` + --- ## Usage diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..3188e62 --- /dev/null +++ b/compose.yml @@ -0,0 +1,23 @@ +services: + minimalytics: + build: . + environment: + - MINIM_PORT=${MINIM_PORT:-3333} + volumes: + - minim-data:/root/.minim + restart: unless-stopped + + haproxy: + build: ./proxy + ports: + - "${PROXY_PORT:-3334}:80" + environment: + - AUTH_USER=${AUTH_USER:-admin} + - AUTH_PASS=${AUTH_PASS:-changeme} + - MINIM_PORT=${MINIM_PORT:-3333} + depends_on: + - minimalytics + restart: unless-stopped + +volumes: + minim-data: diff --git a/proxy/Dockerfile b/proxy/Dockerfile new file mode 100644 index 0000000..63e90ef --- /dev/null +++ b/proxy/Dockerfile @@ -0,0 +1,10 @@ +# Use same Alpine version as minimalytics for layer sharing +FROM alpine:3.20 + +RUN apk add --no-cache haproxy + +COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg + +EXPOSE 80 + +CMD ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"] diff --git a/proxy/haproxy.cfg b/proxy/haproxy.cfg new file mode 100644 index 0000000..6a3d6bc --- /dev/null +++ b/proxy/haproxy.cfg @@ -0,0 +1,32 @@ +global + log stdout format raw local0 + +defaults + log global + mode http + option httplog + timeout connect 5s + timeout client 30s + timeout server 30s + +userlist auth_users + user "${AUTH_USER}" insecure-password "${AUTH_PASS}" + +frontend http_front + bind *:80 + + # ACL for event API - no auth required + acl is_event_api path_beg /api/event/ + + # Route event API without auth + use_backend minimalytics_noauth if is_event_api + + # Everything else requires auth + default_backend minimalytics_auth + +backend minimalytics_noauth + server app minimalytics:"${MINIM_PORT}" check + +backend minimalytics_auth + http-request auth unless { http_auth(auth_users) } + server app minimalytics:"${MINIM_PORT}" check