Skip to content
Open
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
141 changes: 141 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Build + publish the four Nexus container images to Docker Hub.
#
# Trigger: version tags (v*) or manual dispatch. Correctness is gated by
# ci.yml / go-ci.yml on every PR — this workflow gates PUBLISHING:
# Gate A (in-Dockerfile): libhs HSSelfTest during build + binary tag check.
# Gate B (here): hs-selfcheck executed inside the built runtime image —
# proves hs_compile/hs_alloc_scratch/hs_scan work in the exact
# environment we ship. "Build succeeded" alone would miss a
# FAT_RUNTIME=ON libhs that silently never scans (PII passthrough).
# Gate C (here): full-stack compose smoke — db-init seeds, all four services
# report healthy, the runtime hs-selfcheck confirms the scan engine,
# and the seeded virtual key authenticates + runs the request
# pipeline (see deploy/docker/smoke-redaction.sh). Credential-free;
# full PII-redaction E2E is tests/scripts/smoke-gateway.py against a
# credentialled deployment.
#
# Secrets required: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (alphabitcore org).
# Arch: linux/amd64 (arm64 is a planned fast-follow — per-arch libhs builds
# make it additive; see deploy/docker/ai-gateway Dockerfile notes).

name: docker-publish

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Image tag to publish (defaults to the git ref name)"
required: false

env:
REGISTRY_ORG: alphabitcore

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- image: nexus-hub
dockerfile: packages/nexus-hub/Dockerfile
vectorscan: false
- image: nexus-console
dockerfile: deploy/docker/console/Dockerfile
vectorscan: false
- image: nexus-ai-gateway
dockerfile: packages/ai-gateway/Dockerfile
vectorscan: true
- image: nexus-compliance-proxy
dockerfile: packages/compliance-proxy/Dockerfile
vectorscan: true
# Deploy-utility image (not a product image) — the Helm db-init hook
# needs schema+seed in a runnable image; compose mounts host source.
- image: nexus-db-migrate
dockerfile: tools/db-migrate/Dockerfile
vectorscan: false
steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- name: Build image (amd64)
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
load: true
tags: ${{ env.REGISTRY_ORG }}/${{ matrix.image }}:ci
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,scope=${{ matrix.image }},mode=max

- name: "Gate B: vectorscan runtime self-check"
if: ${{ matrix.vectorscan }}
run: |
docker run --rm --entrypoint hs-selfcheck \
"$REGISTRY_ORG/${{ matrix.image }}:ci"

- name: Export image for downstream jobs
run: |
mkdir -p /tmp/images
docker save "$REGISTRY_ORG/${{ matrix.image }}:ci" \
-o "/tmp/images/${{ matrix.image }}.tar"

- uses: actions/upload-artifact@v4
with:
name: image-${{ matrix.image }}
path: /tmp/images/${{ matrix.image }}.tar
retention-days: 1

compose-smoke:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
pattern: image-*
path: /tmp/images
merge-multiple: true

- name: Load built images
run: for f in /tmp/images/*.tar; do docker load -i "$f"; done

- name: "Gate C: full-stack smoke (health + PII redaction path)"
run: |
./scripts/compose-init.sh
NEXUS_IMAGE_TAG=ci \
deploy/docker/smoke-redaction.sh

push:
needs: compose-smoke
runs-on: ubuntu-latest
strategy:
matrix:
image: [nexus-hub, nexus-console, nexus-ai-gateway, nexus-compliance-proxy, nexus-db-migrate]
steps:
- uses: actions/download-artifact@v4
with:
name: image-${{ matrix.image }}
path: /tmp/images

- name: Load built image
run: docker load -i /tmp/images/${{ matrix.image }}.tar

- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Tag + push
run: |
TAG="${{ github.event.inputs.tag || github.ref_name }}"
SHA_TAG="${GITHUB_SHA::12}"
for t in "$TAG" "$SHA_TAG" latest; do
docker tag "$REGISTRY_ORG/${{ matrix.image }}:ci" \
"$REGISTRY_ORG/${{ matrix.image }}:$t"
docker push "$REGISTRY_ORG/${{ matrix.image }}:$t"
done
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,9 @@ worktrees/

# Local tool installs (golangci-lint etc.) — not committed
.tooling/

# Generated by scripts/compose-init.sh — full-stack compose secrets
deploy/docker/.env.compose

# Helm subchart archives — fetched via `helm dependency build` (Chart.lock pins versions)
deploy/helm/nexus-gateway/charts/*.tgz
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,35 @@ The lateral dotted arrow is the **attestation handoff**: the Agent always egress
|---|---|---|
| **AWS Marketplace AMI / single-instance appliance** | `cd nexus-ami && ./build.sh` — bakes binaries + UI + Prisma + nginx + Postgres + Valkey + NATS into one AL2023 image via Packer | [`nexus-ami/README.md`](./nexus-ami/README.md) for build steps, [`docs/developers/architecture/cross-cutting/deployment/ami-appliance-architecture.md`](./docs/developers/architecture/cross-cutting/deployment/ami-appliance-architecture.md) for design |
| **Local development** | docker-compose + `./scripts/dev-start.sh` (Postgres + Valkey + NATS) and per-service `go run ./cmd/<svc>/` | See **Quick start** below |
| **Docker (dev / demo)** | `./scripts/compose-init.sh && docker compose -f docker-compose.full.yml --env-file deploy/docker/.env.compose up -d` — full stack from the published `alphabitcore/nexus-*` images | See **Docker images** below |
| **Kubernetes / Helm (production)** | `helm install nexus deploy/helm/nexus-gateway` — 4 service deployments + embedded or external Postgres/Valkey/NATS | [`deploy/helm/nexus-gateway/README.md`](./deploy/helm/nexus-gateway/README.md) |
| **VMware / KVM image / bare-metal appliance** | Reuses the same `install.sh` + `harden.sh` from `nexus-ami/scripts/` under a different Packer builder | Future |
| **Container / Kubernetes** | Out of scope for the appliance form factor — separate product line | Future |

### Docker images

Four images ship to Docker Hub under the `alphabitcore` org (tag-triggered by
`.github/workflows/docker-publish.yml`; dependency stores — Postgres, Valkey,
NATS — are pulled upstream, never rebuilt):

| Image | Contents | Ports |
|---|---|---|
| `alphabitcore/nexus-console` | nginx + control-plane + admin UI (the console) | 80, 443, 3001 |
| `alphabitcore/nexus-hub` | Hub (node lifecycle, config sync, MQ consumers) | 3060 |
| `alphabitcore/nexus-ai-gateway` | AI Gateway — Vectorscan-accelerated compliance scanning | 3050 |
| `alphabitcore/nexus-compliance-proxy` | TLS-bump compliance proxy — Vectorscan-accelerated | 3128, 3040, 9090 |

```bash
docker pull alphabitcore/nexus-ai-gateway:latest
# verify the scanning engine inside the shipped image (never trust "build succeeded"):
docker run --rm --entrypoint hs-selfcheck alphabitcore/nexus-ai-gateway:latest
# single service against your own infra (vars documented in .env.example):
docker run --env-file ai-gateway.env -p 3050:3050 alphabitcore/nexus-ai-gateway:latest
```

The ai-gateway and compliance-proxy images build libhs from source per-arch
with `FAT_RUNTIME=OFF` + `BUILD_AVX512=OFF` (portable baseline; see the
warning below) and hard-fail the build if the engine didn't link or its
self-test fails — a silent pure-Go RE2 fallback image can never ship.

> **⚠ Building from source — Vectorscan binaries are CPU-microarchitecture-specific.**
> The compliance scanner links **libhs (Vectorscan) statically with `FAT_RUNTIME=OFF`** —
Expand Down
94 changes: 94 additions & 0 deletions deploy/docker/ai-gateway/config.yaml.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Nexus AI Gateway — container-shape config template.
# Rendered by the image entrypoint via envsubst; service addresses default to
# the docker-compose / Helm service names. Secrets are NOT templated here —
# they stay blank and are loaded from the environment by the config loader
# (DATABASE_URL, REDIS_PASSWORD, ADMIN_KEY_HMAC_SECRET,
# CREDENTIAL_ENCRYPTION_KEY, INTERNAL_SERVICE_TOKEN).

# Externally-reachable base URL clients use to reach this gateway (reported to
# the Thing Registry as staticInfo; the admin UI renders it). Required.
publicURL: "${NEXUS_AI_GATEWAY_PUBLIC_URL}"

server:
port: 3050
readTimeout: "30s"
writeTimeout: "360s"

database:
url: "" # env DATABASE_URL

redis:
mode: standalone
addrs: ["${NEXUS_REDIS_ADDR}"]
username: ""
password: "" # env REDIS_PASSWORD
db: 0
sentinel:
masterName: ""
username: ""
password: ""
cluster:
maxRedirects: 8
routeRandomly: false
readOnly: false
tls:
enabled: false
insecureSkipVerify: false
caFile: ""
certFile: ""
keyFile: ""
serverName: ""
poolSize: 200
minIdleConns: 50
maxRetries: 3
dialTimeout: 5s
readTimeout: 3s
writeTimeout: 3s
poolTimeout: 4s

auth:
hmacSecret: "" # env ADMIN_KEY_HMAC_SECRET
credentialMasterKey: "" # env CREDENTIAL_ENCRYPTION_KEY (64 hex chars)
credentialKeyMap: ""
internalServiceToken: "" # env INTERNAL_SERVICE_TOKEN

log:
level: "${NEXUS_LOG_LEVEL}"
format: "json"
file: "" # empty = stdout (container-native logging)

registry:
nexusHubUrl: "${NEXUS_HUB_URL}"

mq:
driver: "nats"
nats:
url: "${NEXUS_NATS_URL}"

cors:
enabled: false
allowedOrigins: []
allowedMethods: ["GET", "POST", "OPTIONS"]
allowedHeaders: ["Content-Type", "Authorization", "x-nexus-virtual-key", "x-request-id"]
maxAgeSec: 600

cache:
enabled: true
ttl: 5m
prefix: "ai-gw:"
broker: true

otel:
endpoint: ""
serviceName: "nexus-ai-gateway"

observability:
latencyDetail: true

routing:
defaultRetryPolicy:
maxAttemptsPerTarget: 1
retryOn: ["network", "timeout", "429", "5xx"]
backoffInitial: 250ms
backoffMax: 5s
backoffJitter: 0.2
16 changes: 16 additions & 0 deletions deploy/docker/ai-gateway/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
# Nexus AI Gateway container entrypoint — renders the config template with the
# service addresses for this deployment, then execs the gateway.
set -eu

: "${NEXUS_REDIS_ADDR:=valkey:6379}"
: "${NEXUS_NATS_URL:=nats://nats:4222}"
: "${NEXUS_HUB_URL:=http://nexus-hub:3060}"
: "${NEXUS_AI_GATEWAY_PUBLIC_URL:=http://localhost:3050}"
: "${NEXUS_LOG_LEVEL:=info}"
export NEXUS_REDIS_ADDR NEXUS_NATS_URL NEXUS_HUB_URL NEXUS_AI_GATEWAY_PUBLIC_URL NEXUS_LOG_LEVEL

envsubst '${NEXUS_REDIS_ADDR} ${NEXUS_NATS_URL} ${NEXUS_HUB_URL} ${NEXUS_AI_GATEWAY_PUBLIC_URL} ${NEXUS_LOG_LEVEL}' \
< /opt/nexus/ai-gateway.config.yaml.tpl > /etc/nexus/ai-gateway.config.yaml

exec /usr/local/bin/ai-gateway -config /etc/nexus/ai-gateway.config.yaml
Loading
Loading