From 758372adfaaff4a2297f4b394d16508f57960190 Mon Sep 17 00:00:00 2001 From: PhilippTheServer Date: Wed, 26 Aug 2026 18:59:08 +0200 Subject: [PATCH] build: run the admin UI from a container, like everything else Every other part of OpenTaberna comes up with `docker compose up -d`. The admin UI was the exception, running only from source, so bringing the whole system up meant remembering that one piece worked differently. Adds Dockerfile, nginx.conf, compose.yaml and .dockerignore mirroring OpenTaberna/frontend: a multi-stage build serving the built app from nginx on port 4200. The API is now addressed as /api rather than a hardcoded http://localhost:8000. nginx maps it in the container and proxy.conf.json maps it for ng serve, so both ways of running the app behave identically and neither carries an API host in the bundle. The Keycloak URL stays absolute, because the browser is redirected there and a same-origin path would be meaningless. Closes #13 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YY1ekLLeFLkAU2kvdQ8Ey4 --- .dockerignore | 5 +++++ Dockerfile | 12 ++++++++++++ angular.json | 9 +++++---- compose.yaml | 10 ++++++++++ nginx.conf | 16 ++++++++++++++++ proxy.conf.json | 8 ++++++++ src/environments/environment.ts | 11 +++++++++-- tsconfig.app.json | 8 ++------ tsconfig.spec.json | 9 ++------- 9 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 compose.yaml create mode 100644 nginx.conf create mode 100644 proxy.conf.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0847e88 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +.angular +.git +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e9224c6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:24.15-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist/admin-frontend/browser /usr/share/nginx/html +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1 diff --git a/angular.json b/angular.json index 57ef391..f9219ab 100644 --- a/angular.json +++ b/angular.json @@ -25,9 +25,7 @@ "input": "public" } ], - "styles": [ - "src/styles.css" - ] + "styles": ["src/styles.css"] }, "configurations": { "production": { @@ -63,7 +61,10 @@ "buildTarget": "admin-frontend:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "proxy.conf.json" + } }, "test": { "builder": "@angular/build:unit-test" diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..74f8fce --- /dev/null +++ b/compose.yaml @@ -0,0 +1,10 @@ +services: + admin: + build: + context: . + dockerfile: Dockerfile + ports: + - "4200:80" + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a752a20 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://host.docker.internal:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/proxy.conf.json b/proxy.conf.json new file mode 100644 index 0000000..83454af --- /dev/null +++ b/proxy.conf.json @@ -0,0 +1,8 @@ +{ + "/api": { + "target": "http://localhost:8000", + "secure": false, + "changeOrigin": true, + "pathRewrite": { "^/api": "" } + } +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 5337bd8..13d8fe0 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,7 +1,14 @@ /** * Runtime configuration for local development. * - * `apiBaseUrl` points at the FastAPI service; `keycloak` matches the realm + * `apiBaseUrl` is a same-origin path, not a host. Both ways of running this app + * map `/api` onto the FastAPI service — nginx does it in the container, + * `proxy.conf.json` does it for `ng serve` — so the application never carries a + * hardcoded API host and the two behave identically. + * + * The Keycloak URL stays absolute because the browser is redirected to it, so a + * same-origin path would be meaningless there. + * `keycloak` matches the realm * committed in the API repository (`keycloak/opentaberna-realm.json`). * * The client id matters: the API only accepts tokens on `/v1/admin/**` whose @@ -10,7 +17,7 @@ */ export const environment = { production: false, - apiBaseUrl: 'http://localhost:8000', + apiBaseUrl: '/api', /** * Uncaught error reporting. Off by default; requires FRONTEND_ERRORS_ENABLED * on the API, which otherwise answers the endpoint with a 404. diff --git a/tsconfig.app.json b/tsconfig.app.json index cb151e1..1eb42f4 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,10 +5,6 @@ "compilerOptions": { "types": [] }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.spec.ts" - ] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 9c8efb9..aecce35 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -3,12 +3,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "types": [ - "vitest/globals" - ] + "types": ["vitest/globals"] }, - "include": [ - "src/**/*.d.ts", - "src/**/*.spec.ts" - ] + "include": ["src/**/*.d.ts", "src/**/*.spec.ts"] }