mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 16:41:09 +00:00
refactor: remove deploy.cmd and update Dockerfile for health checks
- 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>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user