This commit is contained in:
Bastian Wagner
2026-07-15 14:31:56 +02:00
parent 3e5348b7ec
commit 0fd85573d2
11 changed files with 128 additions and 14 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
dist
.angular
.git
.env
coverage
npm-debug.log*
apps/api/dist
apps/web/dist

View File

@@ -3,6 +3,7 @@ NODE_ENV=development
API_PORT=3000 API_PORT=3000
WEB_PORT=4200 WEB_PORT=4200
PUBLIC_WEB_URL=http://localhost: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_URL=mysql://ldap_portal:change-me@mysql.example.com:3306/ldap_portal
DATABASE_SSL=false DATABASE_SSL=false

34
Dockerfile Normal file
View File

@@ -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"]

View File

@@ -35,6 +35,33 @@ docker compose up --build
Passe vor dem Start mindestens `DATABASE_URL`, `JWT_SECRET`, `TOKEN_SECRET`, `LLDAP_*` und `SMTP_*` an. 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. 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 ## Externe Dienste
Die Anwendung bringt keine Datenbank und keinen LLDAP-Server mehr per Compose mit. Erwartet werden: Die Anwendung bringt keine Datenbank und keinen LLDAP-Server mehr per Compose mit. Erwartet werden:

View File

@@ -18,7 +18,7 @@
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": ["zone.js"], "polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"], "assets": ["src/favicon.ico", "src/config.js"],
"styles": ["src/styles.css"] "styles": ["src/styles.css"]
}, },
"configurations": { "configurations": {

3
apps/web/src/config.js Normal file
View File

@@ -0,0 +1,3 @@
window.__LDAP_PORTAL_CONFIG__ = {
apiBaseUrl: 'http://localhost:3000'
};

View File

@@ -5,6 +5,7 @@
<title>LDAP Portal</title> <title>LDAP Portal</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/config.js"></script>
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>

View File

@@ -19,6 +19,14 @@ import { VerifyEmailComponent } from './app/pages/verify-email.component';
import { API_BASE_URL } from './app/shared/api-base-url'; import { API_BASE_URL } from './app/shared/api-base-url';
import { AuthService } from './app/shared/auth.service'; import { AuthService } from './app/shared/auth.service';
declare global {
interface Window {
__LDAP_PORTAL_CONFIG__?: {
apiBaseUrl?: string;
};
}
}
const authInterceptor: HttpInterceptorFn = (request, next) => { const authInterceptor: HttpInterceptorFn = (request, next) => {
const token = localStorage.getItem('accessToken'); const token = localStorage.getItem('accessToken');
if (!token) { if (!token) {
@@ -58,6 +66,6 @@ bootstrapApplication(AppComponent, {
provideRouter(routes), provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])), provideHttpClient(withInterceptors([authInterceptor])),
AuthService, 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)); }).catch((error) => console.error(error));

View File

@@ -1,20 +1,12 @@
services: services:
api: app:
build: build:
context: . context: .
dockerfile: apps/api/Dockerfile dockerfile: Dockerfile
env_file: env_file:
- .env - .env
ports:
- "3000:3000"
web:
build:
context: .
dockerfile: apps/web/Dockerfile
environment: environment:
API_BASE_URL: http://localhost:3000 API_BASE_URL: http://localhost:3000
ports: ports:
- "4200:80" - "3000:3000"
depends_on: - "8080:8080"
- api

View File

@@ -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;
}
}

View File

@@ -0,0 +1,24 @@
#!/bin/sh
set -eu
cat >/usr/share/nginx/html/config.js <<EOF
window.__LDAP_PORTAL_CONFIG__ = {
apiBaseUrl: "${API_BASE_URL:-http://localhost:3000}"
};
EOF
node /app/api/main.js &
api_pid="$!"
nginx -g "daemon off;" &
nginx_pid="$!"
term() {
kill "$api_pid" "$nginx_pid" 2>/dev/null || true
wait "$api_pid" "$nginx_pid" 2>/dev/null || true
}
trap term INT TERM
wait -n "$api_pid" "$nginx_pid"
term