Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
.git/
**/target/
**/.env
**/.env.*
**/node_modules/
**/.next/
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,23 @@ cd opswarden
cp .env.example .env
# adjust OPSWARDEN_KICKOFF_TOKEN and DATABASE_URL if needed

# 3. Run everything (server + database)
# 3. Run everything (database + server + web UI)
docker compose up --build
```

Check the server responds:
Compose brings up `db`, the `server` on `:8080`, and the production `client_web`
on **`:8081`** (the Next.js UI, also the URL-mode target the desktop build loads).
`client_web` proxies `/api/*` to the server over the compose network; the browser
reaches the WebSocket directly on the server's published `:8080`. If host port
`8081` is already in use, run `CLIENT_WEB_PORT=8091 docker compose up --build`;
the container still listens on `:8081` internally.

Check the services respond:

```bash
curl http://localhost:8080/health # -> {"status":"ok"}
curl http://localhost:8080/about.json # -> service catalog + SHA-256 token
curl http://localhost:8081/en # -> 200, the web UI (FR at /fr)
```

### The project at a glance
Expand Down
2 changes: 1 addition & 1 deletion client-desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Default capability for the OpsWarden desktop shell: core APIs + native notifications on the main window.",
"windows": ["main"],
"remote": {
"urls": ["http://localhost:4242"]
"urls": ["http://localhost:4242", "http://localhost:8081"]
},
"permissions": ["core:default", "notification:default"]
}
36 changes: 36 additions & 0 deletions client-web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# --- client-web/Dockerfile ---
#
# Production web surface for OpsWarden (Next.js), served with `next start` on
# :8081 — the URL-mode target the Tauri desktop build loads. Built from the
# monorepo root context (single root package-lock + npm workspaces). `/api/*` is
# proxied to the server via OPSWARDEN_API_ORIGIN (Compose: http://server:8080),
# resolved at server start; the browser reaches the WebSocket directly on the
# server's published :8080 (NEXT_PUBLIC_WS_URL, baked at build time).

# ---- build ----
FROM node:20-bookworm-slim AS builder
WORKDIR /app
# Manifests first so the `npm ci` layer caches on lockfile changes only.
COPY package.json package-lock.json ./
COPY client-web/package.json ./client-web/package.json
COPY client-desktop/package.json ./client-desktop/package.json
RUN npm ci
# App source + build. Both the client `NEXT_PUBLIC_*` env and the `/api/*` rewrite
# destination are baked into the build, so they are set here as build args.
COPY client-web ./client-web
ARG NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws
ARG OPSWARDEN_API_ORIGIN=http://localhost:8080
ENV NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL}
ENV OPSWARDEN_API_ORIGIN=${OPSWARDEN_API_ORIGIN}
RUN npm run build --workspace client-web

# ---- runtime ----
FROM node:20-bookworm-slim AS runner
WORKDIR /app/client-web
ENV NODE_ENV=production
# Installed deps + the built app. next.config.mjs is read at boot, so
# OPSWARDEN_API_ORIGIN is resolved at runtime from the container env.
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/client-web /app/client-web
EXPOSE 8081
CMD ["/app/node_modules/.bin/next", "start", "-p", "8081"]
9 changes: 8 additions & 1 deletion client-web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { fileURLToPath } from "node:url";
const withNextIntl = createNextIntlPlugin("./i18n/request.ts");
const workspaceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

// Origin the Next server proxies `/api/*` to. Next *bakes* rewrite destinations
// into the build, so this is read at build time: the Compose image is built with
// `OPSWARDEN_API_ORIGIN=http://server:8080` (internal network), while `next dev`
// re-evaluates the config per run and falls back to the host server on :8080.
// The WebSocket is a separate, browser-direct concern (NEXT_PUBLIC_WS_URL).
const apiOrigin = process.env.OPSWARDEN_API_ORIGIN || "http://localhost:8080";

/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
Expand All @@ -14,7 +21,7 @@ const nextConfig = {
return [
{
source: "/api/:path*",
destination: "http://localhost:8080/api/:path*",
destination: `${apiOrigin}/api/:path*`,
},
];
},
Expand Down
32 changes: 31 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ services:
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
OPSWARDEN_AUTOMATION_TEAM_ID: ${OPSWARDEN_AUTOMATION_TEAM_ID:-}
OPSWARDEN_AUTOMATION_NOTIFY_URL: ${OPSWARDEN_AUTOMATION_NOTIFY_URL:-}
OPSWARDEN_WEB_ORIGIN: ${OPSWARDEN_WEB_ORIGIN:-http://localhost:4242}
# The browser-facing web origin in Compose is the client_web service on
# :8081 (used for the OAuth post-login redirect). Dev runs keep :4242.
OPSWARDEN_WEB_ORIGIN: ${OPSWARDEN_WEB_ORIGIN:-http://localhost:8081}
# Google OAuth (optional; blank => Google sign-in disabled).
GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID:-}
GOOGLE_OAUTH_CLIENT_SECRET: ${GOOGLE_OAUTH_CLIENT_SECRET:-}
Expand All @@ -51,5 +53,33 @@ services:
timeout: 3s
retries: 10

# Production web surface (Next.js) — the URL-mode target the desktop build
# loads. Serves the UI on :8081 and proxies /api/* to the server over the
# compose network; the browser hits the server's published :8080 for the WS.
client_web:
build:
context: .
dockerfile: client-web/Dockerfile
args:
# Both are baked into the Next build: the browser hits the server's
# published :8080 for the WebSocket; the Next server proxies /api/* to
# the server over the compose network.
NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-ws://localhost:8080/ws}
OPSWARDEN_API_ORIGIN: http://server:8080
ports:
- "${CLIENT_WEB_PORT:-8081}:8081"
depends_on:
server:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
'node -e "require(''http'').get(''http://localhost:8081/en'',r=>process.exit(r.statusCode<500?0:1)).on(''error'',()=>process.exit(1))"',
]
interval: 10s
timeout: 5s
retries: 12

volumes:
db_data: