From 0fd85573d25f079b4f3bae73ca9f6ab81e971fb1 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Wed, 15 Jul 2026 14:31:56 +0200 Subject: [PATCH] docker --- .dockerignore | 9 ++++++++ .env.example | 1 + Dockerfile | 34 ++++++++++++++++++++++++++++++ README.md | 27 ++++++++++++++++++++++++ apps/web/angular.json | 2 +- apps/web/src/config.js | 3 +++ apps/web/src/index.html | 1 + apps/web/src/main.ts | 10 ++++++++- docker-compose.yml | 16 ++++---------- docker/single-container.nginx.conf | 15 +++++++++++++ docker/start-single-container.sh | 24 +++++++++++++++++++++ 11 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 apps/web/src/config.js create mode 100644 docker/single-container.nginx.conf create mode 100644 docker/start-single-container.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c9a12d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +dist +.angular +.git +.env +coverage +npm-debug.log* +apps/api/dist +apps/web/dist diff --git a/.env.example b/.env.example index 9be13bc..bc86ce4 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ NODE_ENV=development API_PORT=3000 WEB_PORT=4200 PUBLIC_WEB_URL=http://localhost:4200 +API_BASE_URL=http://localhost:3000 DATABASE_URL=mysql://ldap_portal:change-me@mysql.example.com:3306/ldap_portal DATABASE_SSL=false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c9d3e61 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:22-alpine AS deps +WORKDIR /app +COPY package*.json ./ +COPY apps/api/package.json apps/api/package.json +COPY apps/web/package.json apps/web/package.json +RUN npm ci + +FROM deps AS build +COPY tsconfig.base.json ./ +COPY eslint.config.mjs ./ +COPY apps/api apps/api +COPY apps/web apps/web +RUN npm run build + +FROM node:22-alpine AS runtime +WORKDIR /app +ENV NODE_ENV=production +ENV API_PORT=3000 +ENV WEB_PORT=8080 +ENV API_BASE_URL=http://localhost:3000 + +RUN apk add --no-cache nginx + +COPY --from=deps /app/node_modules node_modules +COPY --from=build /app/apps/api/dist api +COPY --from=build /app/apps/web/dist/web/browser /usr/share/nginx/html +COPY apps/api/package.json package.json +COPY docker/single-container.nginx.conf /etc/nginx/http.d/default.conf +COPY docker/start-single-container.sh /usr/local/bin/start-ldap-portal +RUN chmod +x /usr/local/bin/start-ldap-portal \ + && mkdir -p /run/nginx /var/log/nginx + +EXPOSE 3000 8080 +CMD ["start-ldap-portal"] diff --git a/README.md b/README.md index 3549397..31cbc98 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,33 @@ docker compose up --build Passe vor dem Start mindestens `DATABASE_URL`, `JWT_SECRET`, `TOKEN_SECRET`, `LLDAP_*` und `SMTP_*` an. Fuer OIDC muessen zusaetzlich `OIDC_ISSUER`, `OIDC_COOKIE_SECRET`, `OIDC_ADMIN_GROUP` und `OIDC_ADMIN_GROUP_UUID` gesetzt werden. +## Single Container Image + +Das Root-`Dockerfile` baut API und Angular in ein einzelnes Image. Der Container startet: + +- NestJS API / IdP auf Port `3000` +- Nginx Web-UI auf Port `8080` + +Build und Push: + +```bash +docker build -t registry.example.com/ldap-portal/idp:latest . +docker push registry.example.com/ldap-portal/idp:latest +``` + +Start: + +```bash +docker run -d --name ldap-portal-idp \ + --env-file .env \ + -e API_BASE_URL=https://id.example.com \ + -p 3000:3000 \ + -p 8080:8080 \ + registry.example.com/ldap-portal/idp:latest +``` + +`API_BASE_URL` wird beim Containerstart in `/config.js` geschrieben und vom Angular-Frontend gelesen. Setze es auf die aus Browser-Sicht erreichbare API-/IdP-URL. + ## Externe Dienste Die Anwendung bringt keine Datenbank und keinen LLDAP-Server mehr per Compose mit. Erwartet werden: diff --git a/apps/web/angular.json b/apps/web/angular.json index 1ade458..1c45420 100644 --- a/apps/web/angular.json +++ b/apps/web/angular.json @@ -18,7 +18,7 @@ "browser": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", - "assets": ["src/favicon.ico"], + "assets": ["src/favicon.ico", "src/config.js"], "styles": ["src/styles.css"] }, "configurations": { diff --git a/apps/web/src/config.js b/apps/web/src/config.js new file mode 100644 index 0000000..9f68964 --- /dev/null +++ b/apps/web/src/config.js @@ -0,0 +1,3 @@ +window.__LDAP_PORTAL_CONFIG__ = { + apiBaseUrl: 'http://localhost:3000' +}; diff --git a/apps/web/src/index.html b/apps/web/src/index.html index 79216a6..e2a2801 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -5,6 +5,7 @@ LDAP Portal + diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index e6091f6..4874eaf 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -19,6 +19,14 @@ import { VerifyEmailComponent } from './app/pages/verify-email.component'; import { API_BASE_URL } from './app/shared/api-base-url'; import { AuthService } from './app/shared/auth.service'; +declare global { + interface Window { + __LDAP_PORTAL_CONFIG__?: { + apiBaseUrl?: string; + }; + } +} + const authInterceptor: HttpInterceptorFn = (request, next) => { const token = localStorage.getItem('accessToken'); if (!token) { @@ -58,6 +66,6 @@ bootstrapApplication(AppComponent, { provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), AuthService, - { provide: API_BASE_URL, useValue: 'http://localhost:3000' }, + { provide: API_BASE_URL, useValue: window.__LDAP_PORTAL_CONFIG__?.apiBaseUrl ?? 'http://localhost:3000' }, ], }).catch((error) => console.error(error)); diff --git a/docker-compose.yml b/docker-compose.yml index c92c26d..f829be3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,12 @@ services: - api: + app: build: context: . - dockerfile: apps/api/Dockerfile + dockerfile: Dockerfile env_file: - .env - ports: - - "3000:3000" - - web: - build: - context: . - dockerfile: apps/web/Dockerfile environment: API_BASE_URL: http://localhost:3000 ports: - - "4200:80" - depends_on: - - api + - "3000:3000" + - "8080:8080" diff --git a/docker/single-container.nginx.conf b/docker/single-container.nginx.conf new file mode 100644 index 0000000..9e4e49c --- /dev/null +++ b/docker/single-container.nginx.conf @@ -0,0 +1,15 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location = /config.js { + add_header Cache-Control "no-store"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docker/start-single-container.sh b/docker/start-single-container.sh new file mode 100644 index 0000000..d949236 --- /dev/null +++ b/docker/start-single-container.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +cat >/usr/share/nginx/html/config.js </dev/null || true + wait "$api_pid" "$nginx_pid" 2>/dev/null || true +} + +trap term INT TERM + +wait -n "$api_pid" "$nginx_pid" +term