Yuki Takei 1 месяц назад
Родитель
Сommit
e1c3b48d12

+ 33 - 380
.kiro/specs/official-docker-image/design.md

@@ -156,168 +156,41 @@ flowchart LR
     Stage4 -->|tar.gz| Stage5
     Stage4 -->|tar.gz| Stage5
 ```
 ```
 
 
-## Requirements Traceability
-
-| Requirement | Summary | Components | Interfaces | Flows |
-|-------------|---------|------------|------------|-------|
-| 1.1 | DHI base image | base, release stages | — | Build flow |
-| 1.2 | Update syntax directive | Dockerfile header | — | — |
-| 1.3 | Maintain pnpm wget installation | base stage | — | Build flow |
-| 1.4 | Fix frozen-lockfile typo | deps stage | — | — |
-| 1.5 | Non-hardcoded pnpm version | base stage | — | — |
-| 2.1 | GROWI_HEAP_SIZE | docker-entrypoint.ts | Environment variable I/F | Entrypoint flow |
-| 2.2 | cgroup auto-calculation | docker-entrypoint.ts | cgroup fs I/F | Entrypoint flow |
-| 2.3 | No-flag fallback | docker-entrypoint.ts | — | Entrypoint flow |
-| 2.4 | GROWI_OPTIMIZE_MEMORY | docker-entrypoint.ts | Environment variable I/F | Entrypoint flow |
-| 2.5 | GROWI_LITE_MODE | docker-entrypoint.ts | Environment variable I/F | Entrypoint flow |
-| 2.6 | Use --max-heap-size | docker-entrypoint.ts | spawn args | Entrypoint flow |
-| 2.7 | Do not use NODE_OPTIONS | docker-entrypoint.ts | — | Entrypoint flow |
-| 3.1 | Eliminate COPY . . | pruner + deps stages | — | Build flow |
-| 3.2 | Maintain pnpm cache mount | deps, builder stages | — | Build flow |
-| 3.3 | Maintain apt cache mount | base stage | — | Build flow |
-| 3.4 | Exclude .next/cache | builder stage | — | — |
-| 3.5 | bind from=builder pattern | release stage | — | Build flow |
-| 4.1 | Non-root execution | docker-entrypoint.ts | process.setuid/setgid | Entrypoint flow |
-| 4.2 | Exclude unnecessary packages | release stage | — | — |
-| 4.3 | Enhanced .dockerignore | Dockerfile.dockerignore | — | — |
-| 4.4 | --no-install-recommends | base stage | — | — |
-| 4.5 | Exclude build tools | release stage | — | — |
-| 5.1 | OCI labels | release stage | — | — |
-| 5.2 | Maintain EXPOSE | release stage | — | — |
-| 5.3 | Maintain VOLUME | release stage | — | — |
-| 6.1 | Heap size calculation logic | docker-entrypoint.ts | — | Entrypoint flow |
-| 6.2 | Privilege drop exec | docker-entrypoint.ts | process.setuid/setgid | Entrypoint flow |
-| 6.3 | Maintain /data/uploads | docker-entrypoint.ts | fs module | Entrypoint flow |
-| 6.4 | Maintain /tmp/page-bulk-export | docker-entrypoint.ts | fs module | Entrypoint flow |
-| 6.5 | Maintain CMD migrate | docker-entrypoint.ts | execFileSync | Entrypoint flow |
-| 6.6 | Maintain --expose_gc | docker-entrypoint.ts | spawn args | Entrypoint flow |
-| 6.7 | Flag log output | docker-entrypoint.ts | console.log | Entrypoint flow |
-| 6.8 | Written in TypeScript | docker-entrypoint.ts | Node.js type stripping | — |
-| 7.1-7.5 | Backward compatibility | All components | — | — |
-| 8.1 | Replace docker-new → docker | Directory structure | Filesystem | — |
-| 8.2 | Update Dockerfile path references | Dockerfile | — | — |
-| 8.3 | DHI registry login | buildspec.yml | secrets-manager | Build flow |
-| 8.4 | Verify buildspec Dockerfile path | buildspec.yml | — | Build flow |
-
 ## Components and Interfaces
 ## Components and Interfaces
 
 
-| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies | Contracts |
-|-----------|-------------|--------|-------------|-----------------|-----------|
-| Dockerfile | Infrastructure | Docker image build definition | 1.1-1.5, 3.1-3.5, 4.1-4.5, 5.1-5.3, 6.5, 8.2 | DHI images (P0), turbo (P0), pnpm (P0) | — |
-| docker-entrypoint.ts | Infrastructure | Container startup initialization (TypeScript) | 2.1-2.7, 6.1-6.4, 6.6-6.8 | Node.js fs/child_process (P0), cgroup fs (P1) | Batch |
-| Dockerfile.dockerignore | Infrastructure | Build context filter | 4.3 | — | — |
-| buildspec.yml | CI/CD | CodeBuild build definition | 8.3, 8.4 | AWS Secrets Manager (P0), dhi.io (P0) | Batch |
-
-### Infrastructure Layer
-
-#### Dockerfile
+| Component | Domain/Layer | Intent | Key Dependencies |
+|-----------|-------------|--------|-----------------|
+| Dockerfile | Infrastructure | 5-stage Docker image build definition | DHI images, turbo, pnpm |
+| docker-entrypoint.ts | Infrastructure | Container startup initialization (TypeScript) | Node.js fs/child_process, cgroup fs |
+| Dockerfile.dockerignore | Infrastructure | Build context filter | — |
+| buildspec.yml | CI/CD | CodeBuild build definition | AWS Secrets Manager, dhi.io |
 
 
-| Field | Detail |
-|-------|--------|
-| Intent | 5-stage Docker image build definition |
-| Requirements | 1.1-1.5, 3.1-3.5, 4.1-4.5, 5.1-5.3, 6.5, 7.1-7.5 |
+### Dockerfile
 
 
 **Responsibilities & Constraints**
 **Responsibilities & Constraints**
 - 5-stage configuration: `base` → `pruner` → `deps` → `builder` → `release`
 - 5-stage configuration: `base` → `pruner` → `deps` → `builder` → `release`
 - Use of DHI base images (`dhi.io/node:24-debian13-dev` / `dhi.io/node:24-debian13`)
 - Use of DHI base images (`dhi.io/node:24-debian13-dev` / `dhi.io/node:24-debian13`)
 - **No shell or additional binary copying in runtime** (everything is handled by the Node.js entrypoint)
 - **No shell or additional binary copying in runtime** (everything is handled by the Node.js entrypoint)
-- OCI label assignment
-
-**Dependencies**
-- External: `dhi.io/node:24-debian13-dev` — Build base image (P0)
-- External: `dhi.io/node:24-debian13` — Runtime base image (P0)
-- Outbound: pnpm — Dependency management (P0)
-- Outbound: turbo — Build orchestration (P0)
-
-**Contracts**: Batch [x]
-
-##### Stage Definitions
-
-**Stage 1: `base`**
-```
-FROM dhi.io/node:24-debian13-dev AS base
-```
-- Install `ca-certificates`, `wget` via apt-get (build-only)
-- Install pnpm via wget standalone script (version uses script default)
-- pnpm add turbo --global
-
-**Stage 2: `pruner`**
-```
-FROM base AS pruner
-```
-- `COPY . .` to copy the entire monorepo
-- `turbo prune @growi/app --docker` to generate Docker-optimized files
-- Output: `out/json/` (package.json files), `out/pnpm-lock.yaml`, `out/full/` (source)
-
-**Stage 3: `deps`**
-```
-FROM base AS deps
-```
-- `COPY --from=pruner` to copy only json/ and lockfile (for cache efficiency)
-- `pnpm install --frozen-lockfile` for dependency installation
-- `pnpm add node-gyp --global` (for native modules)
-
-**Stage 4: `builder`**
-```
-FROM deps AS builder
-```
-- `COPY --from=pruner` to copy full/ source
-- `turbo run build --filter @growi/app`
-- `pnpm deploy out --prod --filter @growi/app`
-- Package artifacts into tar.gz (maintaining current contents, including `apps/app/tmp`)
 
 
-**Stage 5: `release`**
-```
-FROM dhi.io/node:24-debian13 AS release
-```
-- **No additional binary copying** (no shell, gosu, setpriv, or busybox needed at all)
-- Extract artifacts via `--mount=type=bind,from=builder`
-- COPY `docker-entrypoint.ts`
-- Set OCI labels, EXPOSE, VOLUME
-- `ENTRYPOINT ["node", "/docker-entrypoint.ts"]`
-
-**Implementation Notes**
-- Fallback if `turbo prune --docker` is incompatible with pnpm workspace: optimized COPY pattern (copy lockfile + package.json files first → install → copy source → build)
-- Pulling DHI images requires `docker login dhi.io` (authentication setup needed in CI/CD)
-- No apt-get is needed at all in the release stage (the current gosu installation is completely eliminated)
+**Stage Definitions:**
+- **base**: DHI dev image + pnpm (wget) + turbo + apt packages (`ca-certificates`, `wget`)
+- **pruner**: `COPY . .` + `turbo prune @growi/app --docker`
+- **deps**: COPY json/lockfile from pruner + `pnpm install --frozen-lockfile` + node-gyp
+- **builder**: COPY full source from pruner + `turbo run build` + `pnpm deploy` + artifact packaging
+- **release**: DHI runtime (no shell) + `COPY --from=builder` artifacts + entrypoint + OCI labels + EXPOSE/VOLUME
 
 
-#### docker-entrypoint.ts
-
-| Field | Detail |
-|-------|--------|
-| Intent | Container startup initialization processing (directory setup, heap size calculation, privilege drop, migration execution, app startup). Written in TypeScript, executed directly via Node.js 24 native type stripping |
-| Requirements | 2.1-2.7, 6.1-6.8 |
+### docker-entrypoint.ts
 
 
 **Responsibilities & Constraints**
 **Responsibilities & Constraints**
-- **Written in TypeScript**: Executed directly via Node.js 24 native type stripping (`node docker-entrypoint.ts`). Enums cannot be used (only erasable syntax is allowed)
-- Root privilege initialization processing (implemented with `fs.mkdirSync`, `fs.symlinkSync`, `fs.chownSync`)
-- Heap size determination via 3-tier fallback (cgroup reading via `fs.readFileSync`)
-- Privilege drop via Node.js native `process.setgid()` + `process.setuid()`
-- Direct migration execution via `child_process.execFileSync` (no npm run, no shell needed)
-- App process startup via `child_process.spawn` with SIGTERM/SIGINT forwarding
-- **No external binary dependencies** (uses only Node.js standard library)
-
-**Dependencies**
-- External: Node.js `fs` module — Filesystem operations (P0)
-- External: Node.js `child_process` module — Process startup (P0)
-- External: cgroup filesystem — Memory limit retrieval (P1)
-- Inbound: Environment variables — GROWI_HEAP_SIZE, GROWI_OPTIMIZE_MEMORY, GROWI_LITE_MODE
-
-**Contracts**: Batch [x]
-
-##### Batch / Job Contract
-
-- **Trigger**: On container startup (executed as `ENTRYPOINT ["node", "/docker-entrypoint.ts"]`)
-- **Input / validation**:
-  - `GROWI_HEAP_SIZE`: Positive integer (in MB). Empty string is treated as unset
-  - `GROWI_OPTIMIZE_MEMORY`: Only `"true"` is valid. Anything else is ignored
-  - `GROWI_LITE_MODE`: Only `"true"` is valid. Anything else is ignored
-  - cgroup v2: `/sys/fs/cgroup/memory.max` — Numeric or `"max"` (unlimited)
-  - cgroup v1: `/sys/fs/cgroup/memory/memory.limit_in_bytes` — Numeric (very large value when unlimited)
-- **Output / destination**: Node flags are passed directly as arguments to `child_process.spawn`
-- **Idempotency & recovery**: Executed on every container restart. Idempotent (`fs.mkdirSync` with `recursive: true` ensures safety)
-
-##### Environment Variable Interface
+- Written in TypeScript, executed via Node.js 24 native type stripping (enums not allowed)
+- Directory setup as root (`/data/uploads` + symlink, `/tmp/page-bulk-export`)
+- Heap size determination via 3-tier fallback
+- Privilege drop via `process.setgid()` + `process.setuid()`
+- Migration execution via `child_process.execFileSync` (direct node invocation, no shell)
+- App process startup via `child_process.spawn` with signal forwarding (PID 1 responsibilities)
+- No external binary dependencies
+
+**Environment Variable Interface**
 
 
 | Variable | Type | Default | Description |
 | Variable | Type | Default | Description |
 |----------|------|---------|-------------|
 |----------|------|---------|-------------|
@@ -325,244 +198,24 @@ FROM dhi.io/node:24-debian13 AS release
 | `GROWI_OPTIMIZE_MEMORY` | `"true"` / (unset) | (unset) | Enable the --optimize-for-size flag |
 | `GROWI_OPTIMIZE_MEMORY` | `"true"` / (unset) | (unset) | Enable the --optimize-for-size flag |
 | `GROWI_LITE_MODE` | `"true"` / (unset) | (unset) | Enable the --lite-mode flag |
 | `GROWI_LITE_MODE` | `"true"` / (unset) | (unset) | Enable the --lite-mode flag |
 
 
-##### Heap Size Calculation Logic
-
-```typescript
-// Priority 1: GROWI_HEAP_SIZE env
-// Priority 2: cgroup v2 (/sys/fs/cgroup/memory.max) — 60%
-// Priority 3: cgroup v1 (/sys/fs/cgroup/memory/memory.limit_in_bytes) — 60%, < 64GB
-// Priority 4: undefined (V8 default)
-
-function detectHeapSize(): number | undefined {
-  const envValue: string | undefined = process.env.GROWI_HEAP_SIZE;
-  if (envValue != null && envValue !== '') {
-    const parsed: number = parseInt(envValue, 10);
-    return Number.isNaN(parsed) ? undefined : parsed;
-  }
-
-  // cgroup v2
-  const cgroupV2: number | undefined = readCgroupLimit('/sys/fs/cgroup/memory.max');
-  if (cgroupV2 != null) {
-    return Math.floor(cgroupV2 / 1024 / 1024 * 0.6);
-  }
-
-  // cgroup v1
-  const cgroupV1: number | undefined = readCgroupLimit('/sys/fs/cgroup/memory/memory.limit_in_bytes');
-  if (cgroupV1 != null && cgroupV1 < 64 * 1024 * 1024 * 1024) {
-    return Math.floor(cgroupV1 / 1024 / 1024 * 0.6);
-  }
-
-  return undefined;
-}
-```
-
-##### Node Flags Assembly
-
-```typescript
-const nodeFlags: string[] = ['--expose_gc'];
-
-const heapSize: number | undefined = detectHeapSize();
-if (heapSize != null) {
-  nodeFlags.push(`--max-heap-size=${heapSize}`);
-}
-
-if (process.env.GROWI_OPTIMIZE_MEMORY === 'true') {
-  nodeFlags.push('--optimize-for-size');
-}
-
-if (process.env.GROWI_LITE_MODE === 'true') {
-  nodeFlags.push('--lite-mode');
-}
-```
-
-##### Directory Setup (as root)
-
-```typescript
-import fs from 'node:fs';
-
-// /data/uploads for FILE_UPLOAD=local
-fs.mkdirSync('/data/uploads', { recursive: true });
-if (!fs.existsSync('./public/uploads')) {
-  fs.symlinkSync('/data/uploads', './public/uploads');
-}
-chownRecursive('/data/uploads', 1000, 1000);
-fs.lchownSync('./public/uploads', 1000, 1000);
-
-// /tmp/page-bulk-export
-fs.mkdirSync('/tmp/page-bulk-export', { recursive: true });
-chownRecursive('/tmp/page-bulk-export', 1000, 1000);
-fs.chmodSync('/tmp/page-bulk-export', 0o700);
-```
-
-`chownRecursive` is a helper function that recursively changes ownership using `fs.readdirSync` + `fs.chownSync`.
-
-##### Privilege Drop
-
-```typescript
-process.initgroups('node', 1000);
-process.setgid(1000);
-process.setuid(1000);
-```
-
-The order `setgid` → `setuid` is mandatory (setgid cannot be called after setuid). `initgroups` also initializes supplementary groups.
-
-##### Migration Execution
-
-```typescript
-import { execFileSync } from 'node:child_process';
-
-execFileSync(process.execPath, [
-  '-r', 'dotenv-flow/config',
-  'node_modules/migrate-mongo/bin/migrate-mongo', 'up',
-  '-f', 'config/migrate-mongo-config.js',
-], { stdio: 'inherit', env: { ...process.env, NODE_ENV: 'production' } });
-```
-
-`execFileSync` directly executes the node binary without going through a shell. This achieves equivalent behavior to `npm run migrate` without requiring a shell.
-
-##### App Process Spawn
-
-```typescript
-import { spawn } from 'node:child_process';
-import type { ChildProcess } from 'node:child_process';
-
-const child: ChildProcess = spawn(process.execPath, [
-  ...nodeFlags,
-  '-r', 'dotenv-flow/config',
-  'dist/server/app.js',
-], { stdio: 'inherit', env: { ...process.env, NODE_ENV: 'production' } });
-
-// PID 1 signal forwarding
-const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];
-for (const sig of signals) {
-  process.on(sig, () => child.kill(sig));
-}
-child.on('exit', (code: number | null, signal: NodeJS.Signals | null) => {
-  process.exit(code ?? (signal === 'SIGTERM' ? 0 : 1));
-});
-```
-
-**Implementation Notes**
-- Written in TypeScript and executed directly via Node.js 24 native type stripping. `ENTRYPOINT ["node", "/docker-entrypoint.ts"]`
-- Enums cannot be used (non-erasable syntax). Only interface/type/type annotation are used
-- The entrypoint uses `process.execPath` (= `/usr/local/bin/node`) to execute migration and app, so no shell is needed at all
-- `--max-heap-size` is passed directly as a spawn argument, bypassing NODE_OPTIONS restrictions
-- The migration command directly describes the contents of the `migrate` script from `apps/app/package.json`. When package.json changes, the entrypoint also needs to be updated
-- PID 1 responsibilities: signal forwarding, child process reaping, proper exit code propagation
-
-#### Dockerfile.dockerignore
-
-| Field | Detail |
-|-------|--------|
-| Intent | Exclude unnecessary files from the build context |
-| Requirements | 4.3 |
-
-**Implementation Notes**
-- Entries to add to current: `.git`, `.env*` (except production), `*.md`, `test/`, `**/*.spec.*`, `**/*.test.*`, `.vscode/`, `.idea/`
-- Maintain current: `**/node_modules`, `**/coverage`, `**/Dockerfile`, `**/*.dockerignore`, `**/.pnpm-store`, `**/.next`, `**/.turbo`, `out`, `apps/slackbot-proxy`
+**Batch Contract**
+- **Trigger**: On container startup (`ENTRYPOINT ["node", "/docker-entrypoint.ts"]`)
+- **Input validation**: GROWI_HEAP_SIZE (positive int, empty = unset), GROWI_OPTIMIZE_MEMORY/GROWI_LITE_MODE (only `"true"` is valid), cgroup v2 (`memory.max`: numeric or `"max"`), cgroup v1 (`memory.limit_in_bytes`: numeric, large value = unlimited)
+- **Output**: Node flags passed directly as arguments to `child_process.spawn`
+- **Idempotency**: Executed on every restart, safe via `fs.mkdirSync({ recursive: true })`
 
 
 ## Error Handling
 ## Error Handling
 
 
-### Error Strategy
-
-The entrypoint catches errors at each phase using try-catch. Fatal errors notify Docker/k8s of container startup failure via `process.exit(1)`.
-
-### Error Categories and Responses
-
 | Error | Category | Response |
 | Error | Category | Response |
 |-------|----------|----------|
 |-------|----------|----------|
-| cgroup file read failure | System | Warn with `console.warn` and continue with no flag (V8 default) |
-| GROWI_HEAP_SIZE is invalid (NaN, etc.) | User | Warn with `console.error` and continue with no flag (container still starts) |
-| Directory creation/permission setup failure | System | Container startup failure via `process.exit(1)`. Check volume mount configuration |
-| Migration failure | Business Logic | `execFileSync` throws an exception → `process.exit(1)`. Docker/k8s will restart |
-| App process abnormal exit | System | Propagate child process exit code via `process.exit(code)` |
-
-## Testing Strategy
-
-### Unit Tests
-- Heap size calculation logic in docker-entrypoint.ts: 3 patterns for cgroup v2/v1/none (type-safe testing in TypeScript)
-- Environment variable combinations in docker-entrypoint.ts: GROWI_HEAP_SIZE + GROWI_OPTIMIZE_MEMORY + GROWI_LITE_MODE
-- chownRecursive helper in docker-entrypoint.ts: Verify correct recursive chown on nested directory structures
-- Verify that docker-entrypoint.ts can be directly executed via Node.js 24 type stripping
-
-### Integration Tests
-- Docker build succeeds and all 5 stages complete
-- Start container with `GROWI_HEAP_SIZE=250` set and verify `--max-heap-size=250` on the node process
-- Start container with cgroup memory limit and verify that the auto-calculated `--max-heap-size` is correct
-- Migration executes successfully (via `execFileSync`)
-
-### E2E Tests
-- GROWI + MongoDB start via `docker compose up` and browser access is possible
-- File upload works with `FILE_UPLOAD=local` (verify /data/uploads symlink)
-- Container shuts down gracefully when SIGTERM is sent
-
-## Security Considerations
-
-- **DHI base image**: Up to 95% CVE reduction, SLSA Build Level 3 provenance
-- **No shell needed**: No bash/sh/busybox in runtime. Eliminates command injection attack vectors
-- **No gosu/setpriv needed**: Privilege drop via Node.js native `process.setuid/setgid`. No additional binary attack surface
-- **Non-root execution**: Application runs as node (UID 1000). Root is used only for entrypoint initialization (mkdir/chown)
-- **DHI registry authentication**: `docker login dhi.io` is required in CI/CD. Uses Docker Hub credentials
+| cgroup file read failure | System | Warn and continue with no flag (V8 default) |
+| GROWI_HEAP_SIZE is invalid | User | Warn and continue with no flag (container still starts) |
+| Directory creation/permission failure | System | `process.exit(1)` — check volume mount configuration |
+| Migration failure | Business Logic | `execFileSync` throws → `process.exit(1)` — Docker/k8s restarts |
+| App process abnormal exit | System | Propagate child process exit code |
 
 
 ## Performance & Scalability
 ## Performance & Scalability
 
 
 - **Build cache**: `turbo prune --docker` caches the dependency install layer. Skips dependency installation during rebuilds when only source code changes
 - **Build cache**: `turbo prune --docker` caches the dependency install layer. Skips dependency installation during rebuilds when only source code changes
 - **Image size**: No additional binaries in DHI runtime. Base layer is smaller compared to node:24-slim
 - **Image size**: No additional binaries in DHI runtime. Base layer is smaller compared to node:24-slim
 - **Memory efficiency**: Total heap control via `--max-heap-size` avoids the v24 trusted_space overhead issue. Prevents memory pressure in multi-tenant environments
 - **Memory efficiency**: Total heap control via `--max-heap-size` avoids the v24 trusted_space overhead issue. Prevents memory pressure in multi-tenant environments
-
-## Phase 3: Production Replacement and CI/CD Support
-
-### Directory Replacement
-
-Move the artifacts from `apps/app/docker-new/` to `apps/app/docker/` and delete the old files.
-
-**Replacement targets:**
-
-| Operation | File | Notes |
-|------|---------|------|
-| Delete | `apps/app/docker/Dockerfile` | Old 3-stage Dockerfile (node:20-slim) |
-| Delete | `apps/app/docker/docker-entrypoint.sh` | Old shell entrypoint (uses gosu) |
-| Delete | `apps/app/docker/Dockerfile.dockerignore` | Old dockerignore |
-| Move | `docker-new/Dockerfile` → `docker/Dockerfile` | New 5-stage DHI Dockerfile |
-| Move | `docker-new/docker-entrypoint.ts` → `docker/docker-entrypoint.ts` | New TypeScript entrypoint |
-| Move | `docker-new/docker-entrypoint.spec.ts` → `docker/docker-entrypoint.spec.ts` | Test file |
-| Move | `docker-new/Dockerfile.dockerignore` → `docker/Dockerfile.dockerignore` | New dockerignore |
-| Maintain | `apps/app/docker/codebuild/` | CodeBuild configuration (no changes) |
-| Maintain | `apps/app/docker/README.md` | Docker Hub README |
-
-**Path reference updates:**
-- Dockerfile line 122: `apps/app/docker-new/docker-entrypoint.ts` → `apps/app/docker/docker-entrypoint.ts`
-
-**Existing references not affected (verified via codebase investigation):**
-- `buildspec.yml`: `-f ./apps/app/docker/Dockerfile` — Path remains the same
-- `codebuild.tf`: `buildspec = "apps/app/docker/codebuild/buildspec.yml"` — Same
-- `.github/workflows/release.yml`: `./apps/app/docker/README.md` — Same
-- `.github/workflows/ci-app.yml`: `!apps/app/docker/**` exclusion pattern — Same
-- `apps/app/bin/github-actions/update-readme.sh`: `cd docker` — Same
-
-### buildspec.yml DHI Registry Authentication
-
-`docker login dhi.io` is required for pulling DHI images. According to the [DHI documentation](https://docs.docker.com/dhi/how-to/use/), DHI uses Docker Hub credentials.
-
-**Current buildspec.yml:**
-```yaml
-phases:
-  pre_build:
-    commands:
-      - echo ${DOCKER_REGISTRY_PASSWORD} | docker login --username growimoogle --password-stdin
-```
-
-**Updated:**
-```yaml
-phases:
-  pre_build:
-    commands:
-      # login to docker.io (for push)
-      - echo ${DOCKER_REGISTRY_PASSWORD} | docker login --username growimoogle --password-stdin
-      # login to dhi.io (for DHI base image pull)
-      - echo ${DOCKER_REGISTRY_PASSWORD} | docker login dhi.io --username growimoogle --password-stdin
-```
-
-- Uses the same credentials as Docker Hub (DHI authenticates with Docker Hub accounts)
-- Reuses the existing `DOCKER_REGISTRY_PASSWORD` secret
-- No changes needed to `secretsmanager.tf`

+ 8 - 58
.kiro/specs/official-docker-image/requirements.md

@@ -37,96 +37,46 @@ Modernize and optimize the GROWI official Docker image's Dockerfile (`apps/app/d
 
 
 **Objective:** As an infrastructure administrator, I want the Dockerfile's base image and syntax to comply with the latest best practices, so that security patch application, performance improvements, and maintainability enhancements are achieved
 **Objective:** As an infrastructure administrator, I want the Dockerfile's base image and syntax to comply with the latest best practices, so that security patch application, performance improvements, and maintainability enhancements are achieved
 
 
-#### Acceptance Criteria
-
-1. The Dockerfile shall use Docker Hardened Images (DHI) as the base image. Use `dhi.io/node:24-debian13-dev` for the build stage and `dhi.io/node:24-debian13` for the release stage (glibc-based for performance retention, up to 95% CVE reduction)
-2. The Dockerfile shall update the syntax directive to `# syntax=docker/dockerfile:1` (automatically follows the latest stable version)
-3. The Dockerfile shall maintain the wget standalone script method for pnpm installation (corepack is not adopted because it will be removed from Node.js 25 onwards)
-4. The Dockerfile shall fix the typo `pnpm install ---frozen-lockfile` (three dashes) to `--frozen-lockfile` (two dashes)
-5. The Dockerfile shall avoid hardcoding the pnpm version and leverage the `packageManager` field in `package.json` or the latest version retrieval from the install script
+**Summary**: DHI base images adopted (`dhi.io/node:24-debian13-dev` for build, `dhi.io/node:24-debian13` for release) with up to 95% CVE reduction. Syntax directive updated to auto-follow latest stable. pnpm installed via wget standalone script (corepack not adopted due to planned removal in Node.js 25+). Fixed `---frozen-lockfile` typo and eliminated hardcoded pnpm version.
 
 
 ### Requirement 2: Memory Management Optimization
 ### Requirement 2: Memory Management Optimization
 
 
 **Objective:** As a GROWI operator, I want the Node.js heap size to be appropriately controlled according to container memory constraints, so that the risk of OOMKilled is reduced and memory efficiency in multi-tenant environments is improved
 **Objective:** As a GROWI operator, I want the Node.js heap size to be appropriately controlled according to container memory constraints, so that the risk of OOMKilled is reduced and memory efficiency in multi-tenant environments is improved
 
 
-#### Acceptance Criteria
-
-1. The docker-entrypoint.ts shall pass the value of the `GROWI_HEAP_SIZE` environment variable as the `--max-heap-size` flag to the node process when it is set
-2. While the `GROWI_HEAP_SIZE` environment variable is not set, the docker-entrypoint.ts shall read the cgroup memory limit (v2: `/sys/fs/cgroup/memory.max`, v1: `/sys/fs/cgroup/memory/memory.limit_in_bytes`) and automatically calculate 60% of it as `--max-heap-size`
-3. While the cgroup memory limit cannot be detected (e.g., bare metal) and `GROWI_HEAP_SIZE` is not set, the docker-entrypoint.ts shall not add the `--max-heap-size` flag and defer to V8's default behavior
-4. When the `GROWI_OPTIMIZE_MEMORY` environment variable is set to `true`, the docker-entrypoint.ts shall add the `--optimize-for-size` flag to the node process
-5. When the `GROWI_LITE_MODE` environment variable is set to `true`, the docker-entrypoint.ts shall add the `--lite-mode` flag to the node process (disables TurboFan to reduce RSS to v20-equivalent levels. Used as a last resort when OOMKilled occurs frequently)
-6. The docker-entrypoint.ts shall use `--max-heap-size` and shall not use `--max_old_space_size` (to avoid the trusted_space overhead issue in Node.js 24)
-7. The docker-entrypoint.ts shall pass `--max-heap-size` as a direct argument to the node command, not via `NODE_OPTIONS` (due to Node.js constraints)
+**Summary**: 3-tier heap size fallback implemented in docker-entrypoint.ts: (1) `GROWI_HEAP_SIZE` env var, (2) cgroup v2/v1 auto-calculation at 60%, (3) V8 default. Uses `--max-heap-size` (not `--max_old_space_size`) passed as direct spawn arguments (not `NODE_OPTIONS`). Additional flags: `--optimize-for-size` via `GROWI_OPTIMIZE_MEMORY=true`, `--lite-mode` via `GROWI_LITE_MODE=true`.
 
 
 ### Requirement 3: Build Efficiency and Cache Optimization
 ### Requirement 3: Build Efficiency and Cache Optimization
 
 
 **Objective:** As a developer, I want Docker builds to be fast and efficient, so that CI/CD pipeline build times are reduced and image size is minimized
 **Objective:** As a developer, I want Docker builds to be fast and efficient, so that CI/CD pipeline build times are reduced and image size is minimized
 
 
-#### Acceptance Criteria
-
-1. The Dockerfile shall use `--mount=type=bind` instead of `COPY . .` in the builder stage to avoid including source code in layers
-2. The Dockerfile shall maintain pnpm store cache mounts (`--mount=type=cache,target=...`)
-3. The Dockerfile shall maintain apt-get cache mounts in the build stage
-4. The Dockerfile shall ensure that `.next/cache` is not included in the release stage
-5. The Dockerfile shall use the `--mount=type=bind,from=builder` pattern for artifact transfer from the build stage to the release stage
+**Summary**: `turbo prune --docker` pattern adopted to eliminate `COPY . .` and maximize layer cache (dependency install cached separately from source changes). pnpm store and apt-get cache mounts maintained. `.next/cache` excluded from release stage. Artifact transfer uses `COPY --from=builder` (adapted from design's `--mount=type=bind,from=builder` due to shell-less DHI runtime).
 
 
 ### Requirement 4: Security Hardening
 ### Requirement 4: Security Hardening
 
 
 **Objective:** As a security officer, I want the Docker image to comply with security best practices, so that the attack surface is minimized and the safety of the production environment is improved
 **Objective:** As a security officer, I want the Docker image to comply with security best practices, so that the attack surface is minimized and the safety of the production environment is improved
 
 
-#### Acceptance Criteria
-
-1. The Dockerfile shall run the application as a non-root user (node) (using `process.setuid/setgid` in the Node.js entrypoint)
-2. The Dockerfile shall not install unnecessary packages (build tools such as wget, curl, etc.) in the release stage
-3. The Dockerfile shall ensure that `.git`, `node_modules`, test files, secret files, etc. are not included in the build context via `.dockerignore`
-4. The Dockerfile shall use `--no-install-recommends` with `apt-get install` to prevent installation of unnecessary recommended packages
-5. The Dockerfile shall not include tools only needed at build time (turbo, node-gyp, pnpm, etc.) in the release stage image
+**Summary**: Non-root execution via Node.js native `process.setuid/setgid` (no gosu/setpriv). Release stage contains no unnecessary packages — no shell, no apt, no build tools. Enhanced `.dockerignore` excludes `.git`, secrets, test files, IDE configs. `--no-install-recommends` used for apt-get in build stage.
 
 
 ### Requirement 5: Operability and Observability Improvement
 ### Requirement 5: Operability and Observability Improvement
 
 
 **Objective:** As an operations engineer, I want the Docker image to have appropriate metadata configured, so that management by container orchestrators is facilitated
 **Objective:** As an operations engineer, I want the Docker image to have appropriate metadata configured, so that management by container orchestrators is facilitated
 
 
-#### Acceptance Criteria
-
-1. The Dockerfile shall include OCI standard LABEL annotations (`org.opencontainers.image.source`, `org.opencontainers.image.title`, `org.opencontainers.image.description`, `org.opencontainers.image.vendor`)
-2. The Dockerfile shall maintain `EXPOSE 3000` to document the port
-3. The Dockerfile shall maintain `VOLUME /data` to document the data persistence point
+**Summary**: OCI standard LABEL annotations added (`org.opencontainers.image.source`, `.title`, `.description`, `.vendor`). `EXPOSE 3000` and `VOLUME /data` maintained.
 
 
 ### Requirement 6: Entrypoint and CMD Refactoring
 ### Requirement 6: Entrypoint and CMD Refactoring
 
 
 **Objective:** As a developer, I want the entrypoint script and CMD to have a clear and maintainable structure, so that dynamic assembly of memory flags and future extensions are facilitated
 **Objective:** As a developer, I want the entrypoint script and CMD to have a clear and maintainable structure, so that dynamic assembly of memory flags and future extensions are facilitated
 
 
-#### Acceptance Criteria
-
-1. The docker-entrypoint.ts shall include the heap size calculation logic (3-tier fallback from Requirement 2)
-2. The docker-entrypoint.ts shall assemble the calculated flags as node command arguments and execute via `child_process.spawn` after dropping privileges with `process.setgid` + `process.setuid`
-3. The docker-entrypoint.ts shall maintain directory creation, symbolic link setup, and permission configuration for `/data/uploads` (FILE_UPLOAD=local support)
-4. The docker-entrypoint.ts shall maintain directory creation and permission configuration for `/tmp/page-bulk-export`
-5. The docker-entrypoint.ts shall maintain the current behavior of starting the application after running migrations
-6. The docker-entrypoint.ts shall maintain the `--expose_gc` flag (required for explicit GC calls in batch processing)
-7. When `GROWI_HEAP_SIZE`, cgroup-calculated value, or various optimization flags are set, the docker-entrypoint.ts shall log the content of the applied flags to standard output
-8. The docker-entrypoint.ts shall be written in TypeScript and executed directly using Node.js 24's native TypeScript execution feature (type stripping)
+**Summary**: Entrypoint rewritten in TypeScript (`docker-entrypoint.ts`) executed via Node.js 24 native type stripping. Handles: directory setup (`/data/uploads`, `/tmp/page-bulk-export`), heap size calculation (3-tier fallback), privilege drop (`process.setgid` + `process.setuid`), migration execution (`execFileSync`), app process spawn with signal forwarding. Always includes `--expose_gc`. Logs applied flags to stdout.
 
 
 ### Requirement 7: Backward Compatibility
 ### Requirement 7: Backward Compatibility
 
 
 **Objective:** As an existing Docker image user, I want existing operations to not break when migrating to the new Dockerfile, so that the risk during upgrades is minimized
 **Objective:** As an existing Docker image user, I want existing operations to not break when migrating to the new Dockerfile, so that the risk during upgrades is minimized
 
 
-#### Acceptance Criteria
-
-1. The Docker image shall support application configuration via environment variables (`MONGO_URI`, `FILE_UPLOAD`, etc.) as before
-2. The Docker image shall maintain `VOLUME /data` and preserve compatibility with existing data volume mounts
-3. The Docker image shall maintain the current behavior of listening on port 3000
-4. While memory management environment variables (`GROWI_HEAP_SIZE`, `GROWI_OPTIMIZE_MEMORY`, `GROWI_LITE_MODE`) are not set, the Docker image shall behave substantially equivalent to the existing behavior (Node.js 24 defaults)
-5. The Docker image shall maintain the usage pattern from `docker-compose.yml` / `compose.yaml`
+**Summary**: Full backward compatibility maintained. Environment variables (`MONGO_URI`, `FILE_UPLOAD`, etc.), `VOLUME /data`, port 3000, and docker-compose usage patterns all work as before. Without memory management env vars, behavior is equivalent to V8 defaults.
 
 
 ### Requirement 8: Production Replacement and CI/CD Support
 ### Requirement 8: Production Replacement and CI/CD Support
 
 
 **Objective:** As an infrastructure administrator, I want the artifacts in the docker-new directory to officially replace the existing docker directory and the CI/CD pipeline to operate with the new Dockerfile, so that DHI-based images are used in production builds
 **Objective:** As an infrastructure administrator, I want the artifacts in the docker-new directory to officially replace the existing docker directory and the CI/CD pipeline to operate with the new Dockerfile, so that DHI-based images are used in production builds
 
 
-#### Acceptance Criteria
-
-1. The Docker build configuration shall move all files from `apps/app/docker-new/` (`Dockerfile`, `docker-entrypoint.ts`, `docker-entrypoint.spec.ts`, `Dockerfile.dockerignore`) to `apps/app/docker/`, and delete the old files (old `Dockerfile`, `docker-entrypoint.sh`, old `Dockerfile.dockerignore`). The `codebuild/` directory and `README.md` shall be maintained
-2. The Dockerfile shall update the self-referencing path `apps/app/docker-new/docker-entrypoint.ts` to `apps/app/docker/docker-entrypoint.ts`
-3. The buildspec.yml shall add a login command to the DHI registry (`dhi.io`) in the pre_build phase. Since DHI uses Docker Hub credentials, the existing `DOCKER_REGISTRY_PASSWORD` secret shall be reused
-4. The buildspec.yml shall correctly reference the new Dockerfile path (`./apps/app/docker/Dockerfile`) (verify that no change is needed as it is the same as the current path)
+**Summary**: All files moved from `apps/app/docker-new/` to `apps/app/docker/`, old files deleted. Dockerfile self-referencing path updated. `docker login dhi.io` added to buildspec.yml pre_build phase, reusing existing `DOCKER_REGISTRY_PASSWORD` secret. `codebuild/` directory and `README.md` maintained.

+ 25 - 0
.kiro/specs/official-docker-image/research.md

@@ -229,6 +229,31 @@
 - **Limitations of process.setuid/setgid**: `process.initgroups` is required for supplementary group initialization. The order setgid -> setuid must be strictly followed
 - **Limitations of process.setuid/setgid**: `process.initgroups` is required for supplementary group initialization. The order setgid -> setuid must be strictly followed
 - **docker login requirement for DHI images**: `docker login dhi.io` is required in CI/CD. Security considerations for credential management are needed
 - **docker login requirement for DHI images**: `docker login dhi.io` is required in CI/CD. Security considerations for credential management are needed
 
 
+## Production Implementation Discoveries
+
+### DHI Dev Image Minimal Configuration (Phase 1 E2E)
+
+- **Issue**: The DHI dev image (`dhi.io/node:24-debian13-dev`) did not include the `which` command
+- **Resolution**: Changed pnpm installation from `SHELL="$(which sh)"` to `SHELL=/bin/sh`
+- **Impact**: Minor — only affects the pnpm install script invocation
+
+### Complete Absence of Shell in DHI Runtime Image (Phase 1 E2E)
+
+- **Issue**: The DHI runtime image (`dhi.io/node:24-debian13`) did not have `/bin/sh`. The design planned `--mount=type=bind,from=builder` + `RUN tar -zxf`, but `RUN` instructions require `/bin/sh`
+- **Resolution**:
+  - **builder stage**: Changed from `tar -zcf` to `cp -a` into a staging directory `/tmp/release/`
+  - **release stage**: Changed from `RUN --mount=type=bind... tar -zxf` to `COPY --from=builder --chown=node:node`
+- **Impact**: Design Req 3.5 (`--mount=type=bind,from=builder` pattern) was replaced with `COPY --from=builder`. The security goal of not requiring a shell at runtime was achieved even more robustly
+- **Lesson**: DHI runtime images are truly minimal — `COPY`, `WORKDIR`, `ENV`, `LABEL`, `ENTRYPOINT` are processed by the Docker daemon and do not require a shell
+
+### process.initgroups() Type Definition Gap
+
+- **Issue**: `process.initgroups('node', 1000)` was called for in the design, but implementation was deferred because the type definition does not exist in `@types/node`
+- **Status**: Deferred (Known Issue)
+- **Runtime**: `process.initgroups` does exist at runtime in Node.js 24
+- **Workaround options**: Wait for `@types/node` fix, or use `(process as any).initgroups('node', 1000)`
+- **Practical impact**: Low — the node user in a Docker container typically has no supplementary groups
+
 ## References
 ## References
 
 
 - [Docker Hardened Images Documentation](https://docs.docker.com/dhi/) — Overview and usage of DHI
 - [Docker Hardened Images Documentation](https://docs.docker.com/dhi/) — Overview and usage of DHI

+ 3 - 2
.kiro/specs/official-docker-image/spec.json

@@ -1,9 +1,10 @@
 {
 {
   "feature_name": "official-docker-image",
   "feature_name": "official-docker-image",
   "created_at": "2026-02-20T00:00:00.000Z",
   "created_at": "2026-02-20T00:00:00.000Z",
-  "updated_at": "2026-02-24T00:15:00.000Z",
+  "updated_at": "2026-02-24T12:00:00.000Z",
   "language": "en",
   "language": "en",
-  "phase": "implemented",
+  "phase": "implementation-complete",
+  "cleanup_completed": true,
   "approvals": {
   "approvals": {
     "requirements": {
     "requirements": {
       "generated": true,
       "generated": true,

+ 1 - 29
.kiro/specs/official-docker-image/tasks.md

@@ -118,35 +118,7 @@
   - Verify startup with `docker compose up` and graceful shutdown via SIGTERM
   - Verify startup with `docker compose up` and graceful shutdown via SIGTERM
   - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5_
   - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5_
 
 
-## Known Issues
-
-- [ ] Addition of supplementary groups initialization via `process.initgroups()`
-  - The design in design.md calls for `process.initgroups('node', 1000)`, but implementation was deferred in Phase 1 because the type definition does not exist in `@types/node`
-  - `process.initgroups` does exist at runtime (confirmed in Node.js 24)
-  - Workaround options: Wait for the `@types/node` fix, or use `(process as any).initgroups('node', 1000)` as a workaround
-  - Practical impact is low (the node user in a Docker container typically has no supplementary groups)
-  - _Requirements: 4.1, 6.2_
-
-## Intentional deviations from Design (discovered and addressed during Phase 1 E2E verification)
-
-### Adaptation to DHI dev image minimal configuration
-
-The DHI dev image (`dhi.io/node:24-debian13-dev`) was more minimal than expected, and the `which` command was not included. The following fix was applied:
-
-1. **pnpm installation**: Changed from `SHELL="$(which sh)"` to `SHELL=/bin/sh` (due to absence of the `which` command)
-
-### Adaptation to complete absence of shell in DHI runtime image
-
-The DHI runtime image (`dhi.io/node:24-debian13`) did not have `/bin/sh`. The Design planned to extract artifacts using `--mount=type=bind,from=builder` + `RUN tar -zxf`, but `RUN` instructions require `/bin/sh` and thus could not be executed.
-
-**Resolution**:
-- **builder stage**: Changed from `tar -zcf` to copying with `cp -a` into a staging directory `/tmp/release/`
-- **release stage**: Changed from `RUN --mount=type=bind... tar -zxf` to `COPY --from=builder --chown=node:node`
-- `COPY`, `WORKDIR`, `ENV`, `LABEL`, `ENTRYPOINT` are all processed directly by the Docker daemon and do not require a shell
-
-**Impact**: Design Req 3.5 (`--mount=type=bind,from=builder` pattern) was replaced with the `COPY --from=builder` pattern. The Design's security goal of not requiring a shell at runtime (Req 4.2, 4.5) was achieved even more robustly.
-
-## Phase 2: turbo prune --docker build optimization (next phase)
+## Phase 2: turbo prune --docker build optimization
 
 
 > To be done after runtime is stable in Phase 1. Migrate from the current `COPY . .` + 3-stage structure to a `turbo prune --docker` + 5-stage structure to improve build cache efficiency.
 > To be done after runtime is stable in Phase 1. Migrate from the current `COPY . .` + 3-stage structure to a `turbo prune --docker` + 5-stage structure to improve build cache efficiency.