mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 15:21:10 +00:00
c7b297de83
- Deleted the deploy.cmd script as it was no longer needed. - Updated Dockerfile to include curl for health checks and added a non-root user for improved security. - Modified health check command to use curl for better reliability. - Adjusted docker-compose.test.yml to reflect changes in health check configuration. - Cleaned up appsettings.json and removed unused configuration properties. - Removed Resource entity and related requests from the codebase as part of the architectural shift. - Updated documentation to reflect the removal of hardware binding and related endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
11 KiB
Markdown
229 lines
11 KiB
Markdown
# Azaion Admin API — Containerization
|
|
|
|
**Date**: 2026-05-13 · **Cycle**: 1 · **Status**: planning artifact (no code changes; Dockerfile updates land in Step 7).
|
|
|
|
## 1. Container Inventory
|
|
|
|
The system has only **one runtime container**. The four library components are linked into the API at build time, not shipped separately.
|
|
|
|
| # | Container | Built from | Purpose | Lifetime |
|
|
|---|-----------|------------|---------|----------|
|
|
| 1 | `admin-api` | `Dockerfile` (root) | Single ASP.NET Core 10 service exposing all 17 endpoints | Long-running |
|
|
| 2 | `e2e-runner` | `e2e/Dockerfile` | Black-box test consumer used by CI and local `docker-compose.test.yml` | One-shot (run-and-exit) |
|
|
| 3 | `test-db` | `postgres:16-alpine` (no custom Dockerfile) | Isolated Postgres for tests | One-shot (per CI run) |
|
|
|
|
> `docker.test/Dockerfile` is a leftover placeholder (`FROM alpine:latest; CMD echo hello`) and is unused. **Drift G** — recommend deletion in Step 7 (scripts) cleanup.
|
|
|
|
## 2. Component → Container Mapping
|
|
|
|
| Component | Ships in container? | Notes |
|
|
|-----------|--------------------:|-------|
|
|
| 01 Data Layer | no | Class library `Azaion.Common`, linked into `admin-api` |
|
|
| 02 User Management | no | Class library `Azaion.Services` |
|
|
| 03 Auth & Security | no | Class library `Azaion.Services` |
|
|
| 04 Resource Management | no | Class library `Azaion.Services` |
|
|
| 05 Admin API | **yes** | Hosts the Minimal API process (`Azaion.AdminApi`) |
|
|
|
|
## 3. `admin-api` — Dockerfile Specification
|
|
|
|
| Property | Current value | Planned value (Step 7) | Rationale |
|
|
|----------|---------------|------------------------|-----------|
|
|
| Build base image | `mcr.microsoft.com/dotnet/sdk:10.0` (`--platform=$BUILDPLATFORM`) | unchanged | Matches restriction (.NET 10.0); cross-platform build supported |
|
|
| Runtime base image | `mcr.microsoft.com/dotnet/aspnet:10.0` | **pin by digest** in production (`@sha256:…`) | Restrictions forbid moving off `aspnet:10.0`; digest pin protects against silent base-image churn |
|
|
| Stages | `base` → `build` → `publish` → `final` | unchanged structure; non-root user added in `final` | Existing layout already follows multi-stage best practice |
|
|
| Working dir | `/app` | unchanged | Matches `start-container.sh` mounts |
|
|
| Exposed port | `8080` | unchanged | Bound by Kestrel via `ASPNETCORE_URLS=http://+:8080` |
|
|
| Container user | **root** (current) | `USER app` (UID 1654, GID 1654) | Closes security audit F-6 / AZ-518 (Drift C). Non-existing UID; matches the convention in `mcr.microsoft.com/dotnet/aspnet:8.0+` images |
|
|
| Mount points needing write | `/app/Content`, `/app/logs` | `chown app:app` both directories in the `final` stage | The new non-root user must own the dirs that are bind-mounted from the host |
|
|
| Build arg | `CI_COMMIT_SHA=unknown` | unchanged; populated by Woodpecker | Already wired; surfaces as `AZAION_REVISION` env var inside the container |
|
|
| OCI labels | none on the Dockerfile (CI adds three: `revision`, `created`, `source`) | move the three labels into the Dockerfile so local builds also carry them | Single source of truth; consistent labeling regardless of build origin |
|
|
| Health check | none | `HEALTHCHECK CMD curl -fsS http://localhost:8080/health \|\| exit 1` | Wires into the `/health` endpoint planned in Step 5 (Observability). Until that endpoint exists, fall back to the TCP probe already used in `docker-compose.test.yml`. |
|
|
| Entrypoint | `["dotnet", "Azaion.AdminApi.dll"]` | unchanged | Smallest-possible entrypoint; PID 1 is the .NET process |
|
|
|
|
### Sketch (planning artifact — actual edits land in Step 7)
|
|
|
|
```
|
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:<pinned> AS base
|
|
WORKDIR /app
|
|
EXPOSE 8080
|
|
RUN groupadd -g 1654 app && useradd -u 1654 -g 1654 -m -d /home/app -s /sbin/nologin app \
|
|
&& mkdir -p /app/Content /app/logs && chown -R app:app /app
|
|
|
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
|
ARG TARGETARCH
|
|
WORKDIR /app
|
|
COPY . .
|
|
RUN dotnet restore
|
|
WORKDIR /app/Azaion.AdminApi
|
|
RUN dotnet build "Azaion.AdminApi.csproj" -c Release -o /app/build
|
|
|
|
FROM build AS publish
|
|
RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \
|
|
dotnet publish "Azaion.AdminApi.csproj" -c Release -o /app/publish /p:UseAppHost=false --os linux --arch $arch
|
|
|
|
FROM base AS final
|
|
ARG CI_COMMIT_SHA=unknown
|
|
ARG BUILD_DATE=unknown
|
|
ENV AZAION_REVISION=$CI_COMMIT_SHA
|
|
LABEL org.opencontainers.image.revision="$CI_COMMIT_SHA"
|
|
LABEL org.opencontainers.image.created="$BUILD_DATE"
|
|
LABEL org.opencontainers.image.source="https://git.azaion.com/azaion/admin"
|
|
COPY --from=publish --chown=app:app /app/publish /app/
|
|
USER app
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD curl -fsS http://localhost:8080/health || exit 1
|
|
ENTRYPOINT ["dotnet", "Azaion.AdminApi.dll"]
|
|
```
|
|
|
|
## 4. `e2e-runner` — Dockerfile Specification
|
|
|
|
Existing `e2e/Dockerfile` is sufficient for cycle 1; no changes proposed.
|
|
|
|
| Property | Value | Notes |
|
|
|----------|-------|-------|
|
|
| Base image | `mcr.microsoft.com/dotnet/sdk:10.0` (build + run) | SDK is required because the runner invokes `dotnet test` |
|
|
| Stages | `build` → run | Multi-stage to discard sources from the final image |
|
|
| Working dir | `/test` | Matches `docker-compose.test.yml` |
|
|
| Output dir | `/test-results` | Bind-mounted to `./e2e/test-results` on the host |
|
|
| User | root (acceptable — short-lived, no network exposure, no persistence beyond `/test-results`) | Non-root not required for one-shot CI containers |
|
|
| Loggers | `console`, `trx`, `xunit` | Last one feeds Woodpecker's parser |
|
|
| Entrypoint | `dotnet test Azaion.E2E.dll …` | Already present |
|
|
|
|
## 5. Local Development — `docker-compose.yml`
|
|
|
|
> Currently the project does **not** ship a local-dev compose file. Local devs run the API via `dotnet run` against a host Postgres on port 4312. We add `docker-compose.yml` in Step 7 (scripts) so newcomers get a one-command bring-up.
|
|
|
|
```yaml
|
|
# docker-compose.yml — planning artifact for Step 7
|
|
services:
|
|
api:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
args:
|
|
CI_COMMIT_SHA: dev
|
|
image: azaion/admin:dev-local
|
|
env_file: .env
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
ports:
|
|
- "8080:8080"
|
|
volumes:
|
|
- ./.dev/content:/app/Content
|
|
- ./.dev/logs:/app/logs
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
networks: [azaion-net]
|
|
|
|
db:
|
|
image: postgres:16-alpine
|
|
environment:
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: postgres
|
|
volumes:
|
|
- ./e2e/db-init/00_run_all.sh:/docker-entrypoint-initdb.d/00_run_all.sh:ro
|
|
- ./env/db:/docker-entrypoint-initdb.d/sql:ro
|
|
- dev-db:/var/lib/postgresql/data
|
|
ports:
|
|
- "4312:5432" # match local-dev convention; non-standard host port
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 10s
|
|
networks: [azaion-net]
|
|
|
|
volumes:
|
|
dev-db:
|
|
|
|
networks:
|
|
azaion-net:
|
|
driver: bridge
|
|
```
|
|
|
|
Notes:
|
|
|
|
- The DB schema and roles are bootstrapped from the same SQL files that the test-compose uses (`env/db/*.sql`), so `docker-compose.yml` and `docker-compose.test.yml` produce DB images with identical structure.
|
|
- `.dev/` is added to `.gitignore` and `.dockerignore` in Step 7.
|
|
- `db.ports` exposes `4312:5432` so a developer running the API outside Docker can still hit the same connection string defined in `.env`.
|
|
|
|
## 6. Blackbox Test — `docker-compose.test.yml` (existing)
|
|
|
|
The current file is already aligned with the Step 2 contract (`docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-consumer`). Only one drift to log:
|
|
|
|
| Drift | Description | Resolved In |
|
|
|-------|-------------|-------------|
|
|
| Drift H | `system-under-test.healthcheck` uses a raw bash TCP probe (`exec 3<>/dev/tcp/127.0.0.1/8080`). Once `/health` exists (Step 5), switch to the curl-based probe to actually test the application layer. | Step 5 + Step 7 |
|
|
|
|
No structural change in cycle 1 — the file already brings up Postgres + SUT + e2e-runner on a private network and tears down on test exit.
|
|
|
|
## 7. Image Tagging Strategy
|
|
|
|
| Context | Tag format | Example | Notes |
|
|
|---------|------------|---------|-------|
|
|
| CI build (per push) | `$REGISTRY_HOST/$REGISTRY_IMAGE:${CI_COMMIT_BRANCH}-${TAG_SUFFIX}` | `docker.azaion.com/azaion/admin:dev-arm` | Existing convention from `.woodpecker/02-build-push.yml` |
|
|
| CI build (per push) — additional immutable tag | `$REGISTRY_HOST/$REGISTRY_IMAGE:${CI_COMMIT_SHA:0:12}-${TAG_SUFFIX}` | `docker.azaion.com/azaion/admin:a1b2c3d4e5f6-arm` | **NEW (Drift A resolution)** — gives every CI build an immutable tag the host scripts can pin |
|
|
| Production deploy | the SHA tag from above; never `latest` | `docker.azaion.com/azaion/admin:a1b2c3d4e5f6-arm` | Eliminates the host-pulls-`:latest` / CI-never-pushes-`:latest` mismatch |
|
|
| Local dev | `azaion/admin:dev-local` | — | Built by `docker-compose.yml`; never pushed |
|
|
| Multi-arch (future) | `<image>:<branch>-amd` and `<image>:<branch>-arm` (already matrix-prepared) | — | The Woodpecker matrix is wired; uncomment the `amd64` row when an amd agent is online |
|
|
|
|
> Drift A resolution depends on a CI change (Step 3) and a script change (Step 7). The tag format itself is decided here.
|
|
|
|
## 8. `.dockerignore`
|
|
|
|
Existing `.dockerignore` is sufficient; no changes proposed in cycle 1. It already excludes `bin/`, `obj/`, `.env`, `.git`, IDE folders, `Dockerfile*`, and compose files. The only addition required by the new local-dev compose is `.dev/` — added in Step 7.
|
|
|
|
```
|
|
.dev
|
|
**/.dockerignore
|
|
**/.env
|
|
**/.git
|
|
**/.gitignore
|
|
**/.project
|
|
**/.settings
|
|
**/.toolstarget
|
|
**/.vs
|
|
**/.vscode
|
|
**/.idea
|
|
**/*.*proj.user
|
|
**/*.dbmdl
|
|
**/*.jfm
|
|
**/azds.yaml
|
|
**/bin
|
|
**/charts
|
|
**/docker-compose*
|
|
**/Dockerfile*
|
|
**/node_modules
|
|
**/npm-debug.log
|
|
**/obj
|
|
**/secrets.dev.yaml
|
|
**/values.dev.yaml
|
|
LICENSE
|
|
README.md
|
|
```
|
|
|
|
## 9. Self-verification
|
|
|
|
- [x] Every component has a Dockerfile specification (only Admin API ships; libraries explicitly excluded with rationale).
|
|
- [x] Multi-stage builds specified for every production image.
|
|
- [x] Non-root user planned for `admin-api` (Drift C closed in spec; code change in Step 7).
|
|
- [x] Health check defined for every long-running service (real `/health` planned in Step 5; TCP fallback documented for the interim).
|
|
- [x] `docker-compose.yml` covers all components + Postgres dependency.
|
|
- [x] `docker-compose.test.yml` already enables black-box testing; one observation logged (Drift H).
|
|
- [x] `.dockerignore` defined and reviewed (one addition planned: `.dev/`).
|
|
|
|
## 10. Drifts Logged Here (carried forward)
|
|
|
|
| ID | Severity | Description | Resolved In |
|
|
|----|----------|-------------|-------------|
|
|
| C | Medium | `Dockerfile` final stage runs as root → add `USER app` (UID 1654) | Step 7 |
|
|
| G | Low | Unused `docker.test/Dockerfile` placeholder | Step 7 (delete) |
|
|
| H | Low | `docker-compose.test.yml` health check is TCP-only; upgrade to `/health` once available | Step 5 + Step 7 |
|