Skip to content

Commit c685647

Browse files
committed
Improve recognition pipeline and example coverage
1 parent 0701c70 commit c685647

56 files changed

Lines changed: 5968 additions & 4891 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ PORT=3000
1515
# Higher values = fewer detections but higher accuracy
1616
FACE_DETECTION_CONFIDENCE_THRESHOLD=0.8
1717

18-
# MinIO S3 Configuration
19-
# MinIO root credentials (for Docker Compose)
20-
MINIO_ROOT_USER=minioadmin
21-
MINIO_ROOT_PASSWORD=minioadmin123
18+
# Face Recognition Configuration
19+
# Minimum cosine similarity required before a nearest neighbor is accepted as a match.
20+
# Lower values = more matches but more false accepts
21+
# Higher values = fewer matches but more false rejects
22+
FACE_RECOGNITION_CONFIDENCE_THRESHOLD=0.5
2223

23-
# S3 Client Configuration
24-
# Endpoint: Use http://minio:9000 inside Docker, http://localhost:9000 for local testing
24+
# RustFS S3-Compatible Storage Configuration
25+
# Endpoint: Use http://rustfs:9000 inside Docker, http://localhost:9000 for local testing
2526
S3_ENDPOINT=http://localhost:9000
2627
S3_BUCKET=facevector-engine
27-
S3_ACCESS_KEY=minioadmin
28-
S3_SECRET_KEY=minioadmin123
28+
S3_ACCESS_KEY=rustfsadmin
29+
S3_SECRET_KEY=rustfsadmin
2930
S3_REGION=us-east-1
3031
S3_FORCE_PATH_STYLE=true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# AWS Rekognition Clone
22
models/
33
output/
4+
benchmarks/results/
45
project_data/
56

67
# Claude
@@ -58,6 +59,7 @@ web_modules/
5859

5960
# Optional npm cache directory
6061
.npm
62+
.npm-cache/
6163

6264
# Optional eslint cache
6365
.eslintcache

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24

CLAUDE.md

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
FaceVector Engine - A production-ready face recognition and vector similarity search engine. A Node.js/TypeScript system using ArcFace embeddings for face recognition, RetinaFace for face detection with landmarks, and PostgreSQL with pgvector extension for efficient vector similarity search.
7+
FaceVector Engine - A production-ready face recognition and vector similarity search engine. A Node.js/TypeScript system using InsightFace Buffalo-L embeddings for face recognition, RetinaFace for face detection with landmarks, and PostgreSQL with pgvector extension for efficient vector similarity search.
88

99
**📚 Technical Documentation:** See [TECHNICAL_DETAILS.md](TECHNICAL_DETAILS.md) for comprehensive technical documentation including image processing pipelines, coordinate transformations, model inference workflows, and performance optimizations.
1010

@@ -18,12 +18,12 @@ FaceVector Engine - A production-ready face recognition and vector similarity se
1818
```bash
1919
make install # Install Node dependencies (production only)
2020
make install-dev # Install all dependencies including dev tools
21-
make models # Download ONNX models (arcface.onnx, retinaface_resnet50.onnx) to models/
21+
make models # Download ONNX models (face_recognition.onnx, retinaface_resnet50.onnx) to models/
2222
make chmod-scripts # Make scripts in scripts/ executable
2323
```
2424

2525
**Model Downloads** (`make models`):
26-
- ArcFace (arcfaceresnet100-8.onnx) from HuggingFace → saved as `models/arcface.onnx`
26+
- InsightFace Buffalo-L (`w600k_r50.onnx`) from HuggingFace → saved as `models/face_recognition.onnx`
2727
- RetinaFace ResNet50 from Google Storage → saved as `models/retinaface_resnet50.onnx`
2828
- Models only downloaded if they don't already exist
2929

@@ -56,9 +56,9 @@ make lint-fix # Auto-fix TypeScript code style issues
5656
## Architecture
5757

5858
### Core Flow
59-
1. **Server startup** (`src/server.ts`): Initialize DB connection → Load ONNX models (ArcFace + RetinaFace) → Start Express server
59+
1. **Server startup** (`src/server.ts`): Initialize DB connection → Load ONNX models (InsightFace + RetinaFace) → Start Express server
6060
2. **Face detection** (`src/retinaface.ts`, `src/embedding.ts`): RetinaFace detects faces with configurable thresholds
61-
3. **Embedding generation** (`src/embedding.ts`): ArcFace generates 512-dim vectors from 112x112 RGB images
61+
3. **Embedding generation** (`src/embedding.ts`): InsightFace generates 512-dim vectors from landmark-aligned 112x112 BGR images
6262
4. **Storage** (`src/db.ts`): PostgreSQL with pgvector extension for vector similarity search
6363
- `detected_faces` table: Face metadata and file paths from detection
6464
- `enrolled_customers` table: Customer info with face embeddings for recognition
@@ -67,15 +67,15 @@ make lint-fix # Auto-fix TypeScript code style issues
6767
### Key Components
6868

6969
**ONNX Model Pipeline**:
70-
- `initModels()` (`src/embedding.ts`): Loads ArcFace (embedding) and RetinaFace (detection) ONNX models at startup
70+
- `initModels()` (`src/embedding.ts`): Loads InsightFace (embedding) and RetinaFace (detection) ONNX models at startup
7171
- `detectAllFacesWithRetinaFace(base64)` (`src/embedding.ts`): RetinaFace inference returning all detected faces with bounding boxes
7272
- Input: 640x640 (mobile) or 840x840 (resnet50) resized image
7373
- Multi-scale detection with strides [8, 16, 32]
7474
- Default confidence threshold: 0.8 (VIS_THRESHOLD), configurable via env var FACE_DETECTION_CONFIDENCE_THRESHOLD
7575
- NMS (Non-Maximum Suppression) threshold: 0.4 to filter overlapping boxes
7676
- Returns array of faces sorted by area (largest first) with landmarks (eyes, nose, mouth)
77-
- `preprocessImage(base64)` (`src/embedding.ts`): Resize to 112x112, normalize to [-1, 1] per channel
78-
- `computeEmbedding(preprocessed)` (`src/embedding.ts`): ArcFace inference → 512-dim Float32Array
77+
- `preprocessImage(base64)` (`src/embedding.ts`): Resize to 112x112, BGR channel-first layout, normalize to [-1, 1] per channel
78+
- `computeEmbedding(preprocessed)` (`src/embedding.ts`): InsightFace inference → 512-dim Float32Array
7979
- RetinaFace model (`src/retinaface.ts`): Detects faces with 5 facial landmarks per face
8080

8181
**Database Layer** (`src/db.ts`):
@@ -86,15 +86,15 @@ make lint-fix # Auto-fix TypeScript code style issues
8686
- `face_embeddings(id uuid, embedding vector(512), image_path text, created_at timestamptz)` - Legacy table
8787
- ivfflat index on `enrolled_customers.embedding` for fast similarity search
8888
- Handles collation version mismatches (suggests `make clean` if detected)
89-
- Images stored in MinIO S3 object storage, S3 keys stored in `original_image_path` and `face_image_path` columns
89+
- Images stored in RustFS S3-compatible object storage, S3 keys stored in `original_image_path` and `face_image_path` columns
9090

9191
**Service Layer**:
9292
- `src/services/faceDetectionService.ts`: Detect faces, crop, and store metadata
9393
- `detectAndStoreFaces()`: Main detection workflow
94-
- Uploads original images to MinIO S3: `originals/{uuid}.jpg`
95-
- Uploads cropped faces to MinIO S3: `faces/{face_id}.jpg`
94+
- Uploads normalized original images to RustFS S3: `originals/{uuid}.jpg`
95+
- Uploads cropped faces to RustFS S3: `faces/{face_id}.jpg`
9696
- Stores S3 keys (not file paths) in database
97-
- `src/services/s3Service.ts`: MinIO S3 storage service
97+
- `src/services/s3Service.ts`: RustFS S3-compatible storage service
9898
- `uploadImage()`: Upload image buffer to S3
9999
- `downloadImage()`: Download image from S3 as buffer
100100
- `deleteImage()`: Delete image from S3
@@ -118,7 +118,7 @@ make lint-fix # Auto-fix TypeScript code style issues
118118
**Middleware**:
119119
- `src/middleware/upload.ts`: Multer configuration for multipart file uploads
120120
- Memory storage for file buffers
121-
- Accepts PNG, JPG, WEBP (max 10MB)
121+
- Accepts PNG, JPG, WEBP, AVIF (max 10MB)
122122
- File type validation
123123

124124
**Controllers** (`src/controllers/`):
@@ -153,44 +153,47 @@ make lint-fix # Auto-fix TypeScript code style issues
153153

154154
### Error Handling
155155
- All face-processing endpoints throw `{code: "NO_FACE"}` errors → Returns `{"error": "no_face_detected"}` (400)
156-
- Face detection configured to reject images without faces (e.g., `examples/box.jpeg`)
156+
- Face detection configured to reject images without faces (e.g., `examples/no_face_box.jpeg`)
157157
- Standardized error responses via `responseHelpers.sendErrorResponse()`
158158

159159
### API Input Format & Storage
160160

161161
**Multipart Upload Design:**
162162
- Primary API accepts multipart form-data (actual file uploads)
163163
- Scripts handle file path → multipart conversion automatically
164-
- Images automatically scaled down (max 1920px) for performance
165-
- Supports PNG, JPG, WEBP formats (max 10MB per file)
164+
- Images normalized through Sharp into auto-rotated JPEG buffers, then scaled down (max 1920px) for performance
165+
- Supports PNG, JPG, WEBP, AVIF formats (max 10MB per file)
166166
- API is stateless and production-ready
167167

168168
**ONNX Models:**
169-
- ArcFace: `models/arcface.onnx` (face embeddings)
169+
- Face recognition: `models/face_recognition.onnx` (InsightFace embeddings)
170170
- RetinaFace: `models/retinaface_resnet50.onnx` (face detection)
171171
- Downloaded via `make models` from HuggingFace/Google Storage
172172

173173
**Storage Architecture:**
174-
- **MinIO S3 Object Storage** (bucket: `facevector-engine`):
175-
- `originals/{uuid}.jpg` - Original uploaded images
174+
- **RustFS S3-Compatible Object Storage** (bucket: `facevector-engine`):
175+
- `originals/{uuid}.jpg` - Normalized uploaded images
176176
- `faces/{face_id}.jpg` - Cropped face images
177177
- S3 keys stored in `detected_faces` table columns
178-
- Access MinIO Console at http://localhost:9001
178+
- Access RustFS Console at http://localhost:9001
179179
- **Temporary Files** (ephemeral, inside Docker):
180180
- `/tmp/facevector/cropped_faces/` - Temporary face crops during processing
181181
- Auto-cleaned on container restart, no volume mount needed
182182

183183
## Testing
184184
Use example images in `examples/` folder:
185-
- `elon_musk_1.jpg`, `elon_musk_2.jpg` - Valid face images for testing (same person)
186-
- `elon_musk_trump.jpg` - Multiple faces in one image
187-
- `xijingping.png`, `xijingping_trump.jpeg` - Additional test images
188-
- `box.jpeg` - No face (for testing detection rejection)
185+
- `elon_musk_enroll.jpg`, `elon_musk_positive.jpg` - Valid Elon images for testing (same person)
186+
- `jensen_huang_enroll.jpg`, `jensen_huang_positive.jpg` - Valid Jensen images for testing (same person)
187+
- `elon_musk_trump_mixed_small.jpg`, `xi_jinping_trump_mixed_small.jpeg` - Multiple faces in one image
188+
- `elon_musk_jensen_huang_mixed_large.webp`, `elon_musk_jensen_huang_mixed_large.avif` - Multi-format normalization fixtures
189+
- `elon_musk_profile.jpg`, `trump_jensen_huang_mixed_profile.jpeg` - Hard profile/angle examples
190+
- `xi_jinping_solo.png` - Additional negative test image
191+
- `no_face_box.jpeg` - No face (for testing detection rejection)
189192

190193
Example scripts in `scripts/` (run `make chmod-scripts` first):
191194
```bash
192195
# Main workflow
193-
./scripts/faces-detect.sh examples/elon_musk_1.jpg [identifier]
196+
./scripts/faces-detect.sh examples/elon_musk_enroll.jpg [identifier]
194197
./scripts/faces-get-image.sh <face_id>
195198
./scripts/faces-enroll.sh <face_id> <customer_id> [name]
196199
./scripts/faces-recognize.sh <face_id>
@@ -210,20 +213,20 @@ All scripts accept optional API URL as last parameter (default: http://localhost
210213
**Workflow Example:**
211214
```bash
212215
# 1. Detect face and get face_id
213-
./scripts/faces-detect.sh examples/elon_musk_1.jpg CUST001
216+
./scripts/faces-detect.sh examples/elon_musk_enroll.jpg CUST001
214217
# Response: [{"face_id": "abc-123...", ...}]
215218

216219
# 2. Enroll the customer
217220
./scripts/faces-enroll.sh abc-123... CUST001 "Elon Musk"
218221
# Response: {"customer_id": "xyz-789...", ...}
219222

220223
# 3. Later, detect face in another image of the same person
221-
./scripts/faces-detect.sh examples/elon_musk_2.jpg
224+
./scripts/faces-detect.sh examples/elon_musk_positive.jpg
222225
# Response: [{"face_id": "def-456...", ...}]
223226

224227
# 4. Recognize who it is
225228
./scripts/faces-recognize.sh def-456...
226-
# Response: [{"customer_identifier": "CUST001", "confidence_score": 0.98, ...}]
229+
# Response: [{"customer_identifier": "CUST001", "confidence_score": 0.6708, ...}]
227230
```
228231

229232
## Configuration
@@ -232,15 +235,13 @@ All scripts accept optional API URL as last parameter (default: http://localhost
232235
- `DATABASE_URL`: PostgreSQL connection string (default: `postgres://postgres:postgres@localhost:5432/face_db`)
233236
- `PORT`: API server port (default: 3000)
234237
- `FACE_DETECTION_CONFIDENCE_THRESHOLD`: Face detection confidence threshold 0.0-1.0 (default: 0.8)
235-
- **MinIO S3 Configuration:**
236-
- `MINIO_ROOT_USER`: MinIO admin username (default: minioadmin)
237-
- `MINIO_ROOT_PASSWORD`: MinIO admin password (default: minioadmin123)
238-
- `S3_ENDPOINT`: S3 endpoint URL (default: http://localhost:9000 for local, http://minio:9000 inside Docker)
238+
- **RustFS S3 Configuration:**
239+
- `S3_ENDPOINT`: S3 endpoint URL (default: http://localhost:9000 for local, http://rustfs:9000 inside Docker)
239240
- `S3_BUCKET`: S3 bucket name (default: facevector-engine)
240-
- `S3_ACCESS_KEY`: S3 access key (default: minioadmin)
241-
- `S3_SECRET_KEY`: S3 secret key (default: minioadmin123)
241+
- `S3_ACCESS_KEY`: S3 access key (default: rustfsadmin)
242+
- `S3_SECRET_KEY`: S3 secret key (default: rustfsadmin)
242243
- `S3_REGION`: S3 region (default: us-east-1)
243-
- `S3_FORCE_PATH_STYLE`: Use path-style URLs for MinIO (default: true)
244+
- `S3_FORCE_PATH_STYLE`: Use path-style URLs for RustFS (default: true)
244245

245246
**Configuration Constants** (`src/config/constants.ts`):
246247
- `RETINAFACE.CONFIDENCE_THRESHOLD`: 0.02 (initial detection threshold, filtered by VIS_THRESHOLD later)
@@ -279,8 +280,3 @@ All scripts accept optional API URL as last parameter (default: http://localhost
279280
- `tsx`: TypeScript execution for development
280281
- `eslint`: Code linting
281282
- `@types/*`: TypeScript type definitions
282-
283-
**Removed (no longer used):**
284-
- `uuid`: Replaced with Node.js built-in `crypto.randomUUID()`
285-
- `body-parser`: Replaced with Express built-in `express.json()`
286-
- `zod`: Validation removed (now done at application level)

Dockerfile

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@ COPY models ./models/
2222
# Note: If models directory is empty or missing files, they will be downloaded/cached
2323
RUN --mount=type=cache,target=/tmp/models-cache \
2424
mkdir -p models && \
25-
if [ ! -f models/arcface.onnx ]; then \
26-
if [ -f /tmp/models-cache/arcface.onnx ]; then \
27-
echo "✓ Using cached arcface.onnx from Docker cache" && \
28-
cp /tmp/models-cache/arcface.onnx models/arcface.onnx; \
25+
if [ ! -f models/face_recognition.onnx ]; then \
26+
if [ -f /tmp/models-cache/face_recognition.onnx ]; then \
27+
echo "✓ Using cached face_recognition.onnx from Docker cache" && \
28+
cp /tmp/models-cache/face_recognition.onnx models/face_recognition.onnx; \
2929
else \
30-
echo "↓ Downloading arcface.onnx (this may take a while)..." && \
31-
curl -L -o models/arcface.onnx https://huggingface.co/onnxmodelzoo/arcfaceresnet100-8/resolve/main/arcfaceresnet100-8.onnx && \
32-
cp models/arcface.onnx /tmp/models-cache/arcface.onnx || true && \
33-
echo "✓ arcface.onnx downloaded and cached"; \
30+
echo "↓ Downloading face_recognition.onnx (this may take a while)..." && \
31+
curl -fL -o models/face_recognition.onnx https://huggingface.co/deepghs/insightface/resolve/main/buffalo_l/w600k_r50.onnx && \
32+
cp models/face_recognition.onnx /tmp/models-cache/face_recognition.onnx || true && \
33+
echo "✓ face_recognition.onnx downloaded and cached"; \
3434
fi; \
3535
else \
36-
echo "✓ Using arcface.onnx from host"; \
36+
echo "✓ Using face_recognition.onnx from host"; \
3737
fi && \
3838
if [ ! -f models/retinaface_resnet50.onnx ]; then \
3939
if [ -f /tmp/models-cache/retinaface_resnet50.onnx ]; then \
4040
echo "✓ Using cached retinaface_resnet50.onnx from Docker cache" && \
4141
cp /tmp/models-cache/retinaface_resnet50.onnx models/retinaface_resnet50.onnx; \
4242
else \
4343
echo "↓ Downloading retinaface_resnet50.onnx (this may take a while)..." && \
44-
curl -L -o models/retinaface_resnet50.onnx https://storage.googleapis.com/ailia-models/retinaface/retinaface_resnet50.onnx && \
44+
curl -fL -o models/retinaface_resnet50.onnx https://storage.googleapis.com/ailia-models/retinaface/retinaface_resnet50.onnx && \
4545
cp models/retinaface_resnet50.onnx /tmp/models-cache/retinaface_resnet50.onnx || true && \
4646
echo "✓ retinaface_resnet50.onnx downloaded and cached"; \
4747
fi; \

Makefile

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
include .env
1+
-include .env
22
export
33

4-
.PHONY: install install-dev models dev db up-db up-minio up down logs clean chmod-scripts reset run lint lint-fix test test-integration test-management
4+
.PHONY: install install-dev models dev db up-db up-rustfs up down logs clean chmod-scripts reset run lint lint-fix test test-storage test-integration test-management test-examples verify-recognition-examples benchmark-performance
5+
6+
COMPOSE := docker compose --env-file .env -f docker-compose.yml
57

68
# Install production Node dependencies only (for Docker/production)
79
install:
@@ -19,27 +21,47 @@ lint: install-dev
1921
lint-fix: install-dev
2022
npm run lint:fix
2123

24+
# Run RustFS storage integration test only
25+
test-storage: install-dev up-rustfs
26+
S3_ENDPOINT=http://localhost:9000 npm test -- src/__tests__/storage.test.ts
27+
28+
# Run full test suite
29+
test: install-dev models up-db up-rustfs
30+
S3_ENDPOINT=http://localhost:9000 npm test
31+
2232
# Run integration test only
23-
test-integration: install-dev models up-db up-minio
33+
test-integration: install-dev models up-db up-rustfs
2434
S3_ENDPOINT=http://localhost:9000 npm test -- src/__tests__/integration.test.ts
2535

2636
# Run management test only
27-
test-management: install-dev models up-db up-minio
37+
test-management: install-dev models up-db up-rustfs
2838
S3_ENDPOINT=http://localhost:9000 npm test -- src/__tests__/management.test.ts
2939

40+
# Run examples fixture coverage test only
41+
test-examples: install-dev models up-db up-rustfs
42+
S3_ENDPOINT=http://localhost:9000 npm test -- src/__tests__/examples.test.ts
43+
44+
# Run positive and negative recognition examples against a running API
45+
verify-recognition-examples:
46+
./scripts/verify-recognition-examples.sh
47+
48+
# Run API benchmark against a running API and write reports to benchmarks/results/
49+
benchmark-performance: install-dev
50+
npm run benchmark
51+
3052
# Download ONNX models locally into models/ (only if they don't exist)
3153
models:
3254
@mkdir -p models
33-
@if [ ! -f models/arcface.onnx ]; then \
34-
echo "↓ Downloading arcface.onnx..."; \
35-
curl -L -o models/arcface.onnx https://huggingface.co/onnxmodelzoo/arcfaceresnet100-8/resolve/main/arcfaceresnet100-8.onnx; \
36-
echo "arcface.onnx downloaded"; \
55+
@if [ ! -f models/face_recognition.onnx ]; then \
56+
echo "↓ Downloading face_recognition.onnx..."; \
57+
curl -fL -o models/face_recognition.onnx https://huggingface.co/deepghs/insightface/resolve/main/buffalo_l/w600k_r50.onnx; \
58+
echo "face_recognition.onnx downloaded"; \
3759
else \
38-
echo "arcface.onnx already exists, skipping download"; \
60+
echo "face_recognition.onnx already exists, skipping download"; \
3961
fi
4062
@if [ ! -f models/retinaface_resnet50.onnx ]; then \
4163
echo "↓ Downloading retinaface_resnet50.onnx..."; \
42-
curl -L -o models/retinaface_resnet50.onnx https://storage.googleapis.com/ailia-models/retinaface/retinaface_resnet50.onnx; \
64+
curl -fL -o models/retinaface_resnet50.onnx https://storage.googleapis.com/ailia-models/retinaface/retinaface_resnet50.onnx; \
4365
echo "✓ retinaface_resnet50.onnx downloaded"; \
4466
else \
4567
echo "✓ retinaface_resnet50.onnx already exists, skipping download"; \
@@ -49,35 +71,35 @@ models:
4971

5072
# Launch only the Postgres service via Docker Compose
5173
up-db:
52-
docker compose -f docker-compose.yml up -d db
74+
$(COMPOSE) up -d db
5375

54-
# Launch only the MinIO service via Docker Compose
55-
up-minio:
56-
docker compose -f docker-compose.yml up -d minio
76+
# Launch only the RustFS service via Docker Compose
77+
up-rustfs:
78+
$(COMPOSE) up -d rustfs
5779

58-
# Run the API locally (requires db and minio running)
59-
run: install-dev models up-db up-minio
80+
# Run the API locally (requires db and RustFS running)
81+
run: install-dev models up-db up-rustfs
6082
npm run dev
6183

6284
# Build and start the full stack (API + Postgres) with Docker Compose
6385
up:
64-
docker compose -f docker-compose.yml --env-file .env up --build
86+
$(COMPOSE) up --build
6587

6688
# Stop and remove running containers
6789
down:
68-
docker compose -f docker-compose.yml --env-file .env down
90+
$(COMPOSE) down
6991

7092
# Tail logs from running containers
7193
logs:
72-
docker compose -f docker-compose.yml --env-file .env logs -f
94+
$(COMPOSE) logs -f
7395

7496
# Remove containers, networks, and volumes
7597
clean:
76-
docker compose -f docker-compose.yml down -v
98+
$(COMPOSE) down -v
7799

78100
# Clean and rebuild everything from scratch
79101
reset: clean
80-
docker compose -f docker-compose.yml --env-file .env up --build
102+
$(COMPOSE) up --build
81103

82104
# Make all scripts executable
83105
chmod-scripts:

0 commit comments

Comments
 (0)