0dcbe18c54
Build & Deploy / Build & Push Docker Image (push) Successful in 2m52s
Test / Type Check (all packages) (push) Successful in 57s
Build & Deploy / Deploy to VPS (push) Successful in 3s
Test / API Unit Tests (push) Successful in 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Successful in 45s
Test / Admin Unit Tests (push) Successful in 47s
Test / Dashboard Unit Tests (push) Successful in 42s
Test / API Integration Tests (push) Successful in 59s
Normalize dashboard API base URLs so bare API origins still call /api/v1 endpoints. Reuse the shared dashboard API base on email verification. Update production storefront URL examples to use the root domain and route footer, policy, and workspace storefront paths through Traefik.
515 lines
20 KiB
Markdown
515 lines
20 KiB
Markdown
## Docker Environments
|
|
|
|
Three Docker environments are available:
|
|
|
|
- `Dockerfile.dev` with `docker-compose.dev.yml`
|
|
- `Dockerfile.test` with `docker-compose.test.yml`
|
|
- `Dockerfile.production` with `docker-compose.production.yml`
|
|
- `docker-compose.pgmanage.yml` for a standalone pgManage container
|
|
- `docker-compose.portainer.production.yml` for a standalone production Portainer deployment behind Traefik
|
|
- `docker-compose.registry.production.yml` for a standalone production Docker registry behind Traefik
|
|
- `docker-compose.registry.local.yml` for a Docker registry running on a separate local server with port `5000` exposed directly
|
|
|
|
### Development
|
|
|
|
Use the full dev stack for local work with hot reload and bundled Postgres and Redis:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml --profile full up --build
|
|
```
|
|
|
|
Services:
|
|
|
|
- storefront: `http://localhost:3000`
|
|
- dashboard: `http://localhost:3000/dashboard`
|
|
- admin: `http://localhost:3000/admin`
|
|
- public-site: `http://localhost:3003`
|
|
- api: `http://localhost:4000`
|
|
- pgAdmin: `http://localhost:5050`
|
|
|
|
Each dev app now runs in its own container and can be started independently with a profile tag:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml --profile api up --build
|
|
docker compose -f docker-compose.dev.yml --profile storefront up --build
|
|
docker compose -f docker-compose.dev.yml --profile dashboard up --build
|
|
docker compose -f docker-compose.dev.yml --profile admin up --build
|
|
docker compose -f docker-compose.dev.yml --profile public-site up --build
|
|
docker compose -f docker-compose.dev.yml --profile tools up --build
|
|
docker compose -f docker-compose.dev.yml --profile api --profile storefront --profile dashboard --profile admin up --build
|
|
```
|
|
|
|
Notes:
|
|
|
|
- `api` starts `postgres`, `redis`, and `migrate` automatically through dependencies.
|
|
- frontend profiles also start `api` and its dependencies automatically.
|
|
- `tools` starts only `pgadmin` plus its required `postgres` dependency.
|
|
|
|
On startup, Docker now waits for Postgres to become healthy, runs a one-shot `migrate` service, and only then starts the selected app container. For development, that bootstrap runs `db:generate` every time, but `db:deploy` and `db:seed` only the first time for a persisted dev database, so your local data survives rebuilds and normal restarts.
|
|
|
|
Uploaded assets handled by the API, including vehicle photos and company branding images, are stored in the named Docker volume `api_uploads_dev` at `/var/lib/rentaldrivego/storage` inside the container. They stay available across normal container rebuilds and restarts.
|
|
|
|
Default dev platform administrator:
|
|
|
|
- email: `admin@rentaldrivego.com`
|
|
- password: `changeme123`
|
|
|
|
If you intentionally want a fresh dev bootstrap:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml down -v
|
|
```
|
|
|
|
If you want to keep the database and only apply new schema changes manually:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml run --rm migrate sh -c "npm run db:deploy"
|
|
```
|
|
|
|
pgAdmin dev login:
|
|
|
|
- email: `admin@rentaldrivego.local`
|
|
- email: `admin@rentaldrivego.dev`
|
|
- password: `admin`
|
|
|
|
pgAdmin opens with the dev Postgres server pre-registered as `RentalDriveGo Dev DB`.
|
|
|
|
pgAdmin Postgres connection:
|
|
|
|
- host: `postgres`
|
|
- port: `5432`
|
|
- database: `rentaldrivego`
|
|
- username: `postgres`
|
|
- password: `password`
|
|
|
|
### Standalone pgManage
|
|
|
|
If you want a standalone Postgres management UI without starting the full development stack:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.pgmanage.yml up -d
|
|
```
|
|
|
|
It publishes `http://localhost:8000` with a standard Docker port mapping and persists its data in the named Docker volume `pgmanage_data`.
|
|
From inside the container, connect to the local Postgres service through `host.docker.internal:5432`.
|
|
For local HTTP access, pgManage defaults `PGMANAGE_SECURE_COOKIES=False`. If you expose it behind HTTPS instead, set `PGMANAGE_SECURE_COOKIES=True`.
|
|
If you are using the enterprise image, set `PGMANAGE_LICENSE_KEY` to remove the startup license error.
|
|
|
|
### Test
|
|
|
|
Use the test stack to run repeatable containerized verification:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.test.yml up --build --abort-on-container-exit
|
|
```
|
|
|
|
The test container runs:
|
|
|
|
- `npm run db:deploy`
|
|
- `npm run db:generate`
|
|
- `npm run type-check`
|
|
- `npm run build`
|
|
- `npm run test:api:integration`
|
|
|
|
### Production
|
|
|
|
The production stack runs behind **Traefik** (reverse proxy + automatic HTTPS via Let's Encrypt). All services communicate over a private Docker network (`internal`). Traefik reaches public-facing services via a separate `traefik-proxy` network.
|
|
|
|
#### 1. Point DNS to your server
|
|
|
|
Add an A record for every subdomain to your server's public IP before deploying so Let's Encrypt can issue certificates:
|
|
|
|
| Subdomain | Service |
|
|
|---|---|
|
|
| `rentaldrivego.ma` | storefront and public site |
|
|
| `api.rentaldrivego.ma` | API |
|
|
| `pgmanage.rentaldrivego.ma` | pgManage (DB admin) |
|
|
| `portainer.rentaldrivego.ma` | Portainer |
|
|
| `registry.rentaldrivego.ma` | Docker registry |
|
|
|
|
#### 2. Install Docker and clone the repo
|
|
|
|
```bash
|
|
# Install Docker (if not already installed)
|
|
curl -fsSL https://get.docker.com | sh
|
|
|
|
git clone <repo-url> rentaldrivego
|
|
cd rentaldrivego
|
|
```
|
|
|
|
#### 3. Create the shared Traefik network
|
|
|
|
Only needs to be done once per server. If it already exists this is a no-op.
|
|
|
|
```bash
|
|
docker network create traefik-proxy
|
|
```
|
|
|
|
#### 4. Configure environment variables
|
|
|
|
```bash
|
|
cp .env.docker.production.example .env.docker.production
|
|
```
|
|
|
|
Open `.env.docker.production` and fill in every value. The minimum required secrets are:
|
|
|
|
| Variable | What to set |
|
|
|---|---|
|
|
| `POSTGRES_PASSWORD` | Strong random password |
|
|
| `JWT_SECRET` | Long random string (e.g. `openssl rand -hex 64`) |
|
|
| `ACME_EMAIL` | Your email for Let's Encrypt notifications |
|
|
| `RESEND_API_KEY` | Resend API key (or configure SMTP vars instead) |
|
|
| `PGMANAGE_DOMAIN` | Hostname for pgManage, e.g. `pgmanage.rentaldrivego.ma` |
|
|
|
|
Production now derives `DATABASE_URL` inside the app container from `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` when `DATABASE_URL_FROM_POSTGRES=true`. That avoids Prisma auth failures when the database password contains reserved URL characters such as `@`, `:`, or `/`.
|
|
|
|
The example file uses `rentaldrivego.ma` for the storefront and public site. The dashboard and admin panel are routed under that same host at `/dashboard` and `/admin`.
|
|
Set `PORTAINER_DOMAIN=portainer.rentaldrivego.ma` in `.env.docker.production` to expose Portainer through Traefik.
|
|
Set `PGMANAGE_DOMAIN=pgmanage.rentaldrivego.ma` to expose pgManage through Traefik, and set `PGMANAGE_LICENSE_KEY` if you are running the enterprise image.
|
|
Set `REGISTRY_DOMAIN=registry.rentaldrivego.ma` to expose the Docker registry through Traefik.
|
|
Set `REGISTRY_UPSTREAM_URL=http://<local-registry-server-ip>:5000` on the VPS when the registry itself runs on a different server and Traefik should reverse-proxy to it.
|
|
|
|
If you want to use a self-hosted registry on the same VPS, also set these values in `.env.docker.production`:
|
|
|
|
```text
|
|
REGISTRY_HOST=registry.rentaldrivego.ma
|
|
REGISTRY_USER=<registry-user>
|
|
REGISTRY_PASSWORD=<strong-registry-password>
|
|
REGISTRY_IMAGE=registry.rentaldrivego.ma/rentaldrivego/car_management_system
|
|
```
|
|
|
|
The registry bootstrap script uses `REGISTRY_USER` and `REGISTRY_PASSWORD` to generate the htpasswd file mounted into the registry container, so the same credentials can be used by GitLab CI for `docker login`.
|
|
|
|
#### 4a. Configure the registry for CI deploys
|
|
|
|
If you want GitLab CI to build once and deploy by pulling a prebuilt image on the VPS, configure one of these two modes in GitLab `Settings > CI/CD > Variables`:
|
|
|
|
- Built-in GitLab Container Registry:
|
|
Enable the project registry and use GitLab's default `CI_REGISTRY`, `CI_REGISTRY_USER`, `CI_REGISTRY_PASSWORD`, and `CI_REGISTRY_IMAGE` variables.
|
|
- Explicit registry variables:
|
|
Set `REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASSWORD`, and `REGISTRY_IMAGE` yourself. This works for GitLab Registry, Docker Hub, GHCR, or another OCI registry.
|
|
|
|
If `REGISTRY_*` variables are set, the CI pipeline now uses them as a complete override and requires all four values. If they are unset, the pipeline falls back to GitLab's `CI_REGISTRY_*` variables.
|
|
|
|
For production image builds, also define these GitLab CI/CD variables with their real public URLs:
|
|
|
|
```text
|
|
NEXT_PUBLIC_API_URL=https://api.rentaldrivego.ma/api/v1
|
|
NEXT_PUBLIC_MARKETPLACE_URL=https://rentaldrivego.ma
|
|
NEXT_PUBLIC_STOREFRONT_URL=https://rentaldrivego.ma
|
|
NEXT_PUBLIC_DASHBOARD_URL=https://rentaldrivego.ma/dashboard
|
|
NEXT_PUBLIC_ADMIN_URL=https://rentaldrivego.ma/admin
|
|
```
|
|
|
|
The Docker build now fails if any of those values are missing or still point at `example.com`, because Next.js inlines them into the production frontend bundles at build time.
|
|
|
|
Example explicit values for a local registry exposed through Traefik:
|
|
|
|
```text
|
|
REGISTRY_HOST=registry.rentaldrivego.ma
|
|
REGISTRY_IMAGE=registry.rentaldrivego.ma/rentaldrivego/car_management_system
|
|
REGISTRY_USER=<registry-user>
|
|
REGISTRY_PASSWORD=<registry-password>
|
|
```
|
|
|
|
For the deploy job, add these GitLab CI variables as well:
|
|
|
|
```text
|
|
VPS_IP=<server-ip-or-hostname>
|
|
VPS_USER=<ssh-user>
|
|
SSH_PRIVATE_KEY=<deployment-private-key>
|
|
```
|
|
|
|
`SSH_PRIVATE_KEY` must be an unencrypted private key that OpenSSH can read in a non-interactive job. The pipeline accepts any of these formats:
|
|
|
|
- GitLab `File` variable containing the private key
|
|
- Standard multiline key pasted directly into the variable value
|
|
- Single-line key with literal `\n` newline escapes
|
|
- Base64-encoded private key stored as a single line
|
|
|
|
If your key is passphrase-protected, generate a dedicated deploy key without a passphrase for CI instead of reusing an interactive workstation key.
|
|
|
|
The production compose file reads `APP_IMAGE` and `APP_VERSION` for pull-based deploys. The GitLab deploy job injects those values automatically and now syncs the deployment assets to the VPS before running the server-side deploy script. Production no longer depends on `git pull` during release.
|
|
|
|
The CI pipeline publishes and deploys from the GitLab default branch (`$CI_DEFAULT_BRANCH`). If you change your release branch, update the repository default branch in GitLab instead of hard-coding branch names in `.gitlab-ci.yml`.
|
|
|
|
If you use `docker:dind` on a self-hosted GitLab runner, the runner's Docker executor must be started with `privileged = true`. Without that, `docker:dind` often logs AppArmor or `/sys/kernel/security` mount errors and can become unreliable even when the job container still starts.
|
|
|
|
#### 5. Bootstrap the server once
|
|
|
|
Traefik must be running before the app stack so it can wire up routes at startup. This bootstrap path is for the first server setup or for deliberate source-based rebuilds. Normal production releases should go through GitLab CI.
|
|
|
|
```bash
|
|
bash scripts/docker-prod-up-traefik.sh
|
|
```
|
|
|
|
If you intentionally want to build from source on the server:
|
|
|
|
```bash
|
|
npm run docker:prod:up
|
|
```
|
|
|
|
Or use the helper scripts if you want to start one production container at a time:
|
|
|
|
```bash
|
|
npm run docker:prod:start:traefik
|
|
npm run docker:prod:start:postgres
|
|
npm run docker:prod:start:redis
|
|
npm run docker:prod:start:api
|
|
npm run docker:prod:start:storefront
|
|
npm run docker:prod:start:dashboard
|
|
npm run docker:prod:start:admin
|
|
npm run docker:prod:start:frontends
|
|
npm run docker:prod:start:pgmanage
|
|
npm run docker:prod:start:portainer
|
|
npm run docker:prod:start:registry
|
|
npm run docker:prod:start:all
|
|
npm run docker:prod:start:api && npm run docker:prod:start:frontends
|
|
```
|
|
|
|
Docker will:
|
|
1. Build the monorepo image on the server
|
|
2. Start all app services (`api`, `storefront`, `dashboard`, `admin`, `pgmanage`)
|
|
|
|
Traefik automatically picks up the containers and provisions TLS certificates. Services are live at their `https://` URLs within ~30 seconds.
|
|
|
|
#### 6. Standard release flow
|
|
|
|
Normal production releases should use GitLab CI:
|
|
|
|
1. CI builds and pushes a single versioned image.
|
|
2. CI copies the current deployment assets (`docker-compose.production.yml`, Traefik config, deploy scripts, pgManage override) to the VPS.
|
|
3. CI runs `bash scripts/docker-prod-deploy.sh` on the VPS with `APP_IMAGE` and `APP_VERSION` pinned to the commit being released.
|
|
4. The deploy script pulls the release image, starts Postgres and Redis, runs `npm run db:deploy` as a one-shot migration job, then starts the API and frontends and waits for their health checks.
|
|
|
|
This avoids the old drift where the VPS checkout changed independently through `git pull`, or where API startup silently mixed migrations with application boot.
|
|
|
|
#### 6a. Deploy Portainer
|
|
|
|
Portainer is deployed separately from the main app stack and reuses the shared `traefik-proxy` network managed by Traefik.
|
|
|
|
```bash
|
|
npm run docker:prod:start:portainer
|
|
```
|
|
|
|
Equivalent raw Docker Compose command:
|
|
|
|
```bash
|
|
docker compose -p rentaldrivego-portainer-prod --env-file .env.docker.production -f docker-compose.portainer.production.yml up -d
|
|
```
|
|
|
|
#### 6b. Deploy the registry on a separate local server
|
|
|
|
For the split setup:
|
|
|
|
1. Run the registry on your local server with port `5000` exposed directly.
|
|
2. Set `REGISTRY_UPSTREAM_URL` on the VPS to `http://<local-registry-server-ip>:5000`.
|
|
3. Restart Traefik on the VPS so it renders the dynamic registry upstream file and serves TLS for `REGISTRY_DOMAIN`.
|
|
|
|
On the local registry server:
|
|
|
|
```bash
|
|
npm run docker:registry:local:start
|
|
```
|
|
|
|
Equivalent raw Docker Compose command:
|
|
|
|
```bash
|
|
docker compose -p rentaldrivego-registry-local --env-file .env.docker.production -f docker-compose.registry.local.yml up -d
|
|
```
|
|
|
|
On first start, the script generates `docker/registry/auth/htpasswd` from `REGISTRY_USER` and `REGISTRY_PASSWORD` in `.env.docker.production`.
|
|
|
|
From the VPS, verify the upstream directly before involving Traefik:
|
|
|
|
```bash
|
|
curl -I http://<local-registry-server-ip>:5000/v2/
|
|
```
|
|
|
|
Expected response:
|
|
|
|
```text
|
|
HTTP/1.1 401 Unauthorized
|
|
```
|
|
|
|
Then on the VPS restart Traefik:
|
|
|
|
```bash
|
|
bash scripts/docker-prod-up-traefik.sh
|
|
```
|
|
|
|
After that, verify the public route and login flow:
|
|
|
|
```bash
|
|
docker login registry.rentaldrivego.ma
|
|
docker pull registry:2
|
|
docker tag registry:2 registry.rentaldrivego.ma/smoke-test:latest
|
|
docker push registry.rentaldrivego.ma/smoke-test:latest
|
|
```
|
|
|
|
Uploaded assets handled by the API, including vehicle photos and company branding images, are persisted in the named Docker volume `api_uploads`, mounted inside the API container at `/var/lib/rentaldrivego/storage`. Rebuilding or redeploying the API no longer clears those files as long as that volume is kept.
|
|
|
|
The production helper scripts and the GitLab deploy job now explicitly create the Docker volume `${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}_api_uploads` before starting or redeploying the API stack, so a fresh server bootstrap does not accidentally start the API without its upload storage volume.
|
|
|
|
#### Updating after a code change
|
|
|
|
Push to the GitLab default branch and let the pipeline deploy the pinned image. That is the supported production release path.
|
|
|
|
If CI is unavailable and you need to redeploy the image already published to the registry from the server:
|
|
|
|
```bash
|
|
APP_IMAGE=registry.example.com/rentaldrivego/car_management_system \
|
|
APP_VERSION=<gitlab-commit-sha> \
|
|
REGISTRY_HOST=registry.example.com \
|
|
REGISTRY_USER=<registry-user> \
|
|
REGISTRY_PASSWORD=<registry-password> \
|
|
bash scripts/docker-prod-deploy.sh
|
|
```
|
|
|
|
Only use `npm run docker:prod:up` when you intentionally want a source build on the server.
|
|
|
|
#### Apply database migrations without downtime
|
|
|
|
```bash
|
|
docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml run --rm migrate
|
|
```
|
|
|
|
#### Create the first production admin
|
|
|
|
The repo includes a seed that creates the first `SUPER_ADMIN` if that email does not already exist.
|
|
|
|
```bash
|
|
docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml run --rm \
|
|
-e ADMIN_SEED_EMAIL=rentaldrivego@gmail.com \
|
|
-e ADMIN_SEED_PASSWORD='Qwerty00!@#$%' \
|
|
-e ADMIN_SEED_FIRST_NAME=Super \
|
|
-e ADMIN_SEED_LAST_NAME=Admin \
|
|
api npm run db:seed:admin
|
|
```
|
|
|
|
Then sign in at `https://rentaldrivego.ma/admin/login` and create any additional admin users from the admin panel.
|
|
|
|
#### View logs
|
|
|
|
```bash
|
|
# All services
|
|
npm run docker:prod:logs
|
|
|
|
# Single service
|
|
npm run docker:prod:logs:api
|
|
```
|
|
|
|
#### Backup production data
|
|
|
|
Create a timestamped backup directory under `./backups`:
|
|
|
|
```bash
|
|
npm run docker:prod:backup
|
|
```
|
|
|
|
Or choose a different parent directory:
|
|
|
|
```bash
|
|
bash scripts/docker-prod-backup.sh /srv/rentaldrivego-backups
|
|
```
|
|
|
|
Each backup contains:
|
|
|
|
- `postgres.dump` — logical PostgreSQL backup in custom format
|
|
- `api-uploads.tar.gz` — uploaded files from `/var/lib/rentaldrivego/storage`
|
|
- `traefik-letsencrypt.tar.gz` — Traefik ACME certificate state, when available
|
|
- `volumes/*.tar.gz` — raw Docker volume archives for the default production volumes
|
|
- `manifest.txt` — basic metadata
|
|
|
|
By default, the backup also archives these named production volumes when they exist:
|
|
|
|
- `${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}_api_uploads`
|
|
- `${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}_pgmanage_prod_data`
|
|
- `${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}_postgres_prod_data`
|
|
- `${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}_redis_prod_data`
|
|
|
|
To include extra named volumes from the same server, pass them as additional arguments:
|
|
|
|
```bash
|
|
bash scripts/docker-prod-backup.sh /srv/rentaldrivego-backups \
|
|
gitlab-l3gq_gitlab-config \
|
|
gitlab-l3gq_gitlab-data \
|
|
gitlab-l3gq_gitlab-logs \
|
|
pgmanage_data \
|
|
traefik-gcjk_portainer_data \
|
|
traefik-gcjk_traefik-letsencrypt
|
|
```
|
|
|
|
You can also provide extra volume names through `DOCKER_EXTRA_BACKUP_VOLUMES`.
|
|
|
|
#### Restore production data
|
|
|
|
Restore is destructive: it overwrites the production database, uploaded files, and, when present in the backup, Traefik ACME state.
|
|
|
|
```bash
|
|
bash scripts/docker-prod-restore.sh ./backups/rentaldrivego-prod-YYYYMMDDTHHMMSSZ --yes
|
|
```
|
|
|
|
The restore script:
|
|
|
|
1. Stops public app services
|
|
2. Restores the PostgreSQL dump
|
|
3. Replaces uploaded files
|
|
4. Restores Traefik ACME state if the archive exists
|
|
5. Restores raw volume archives from `volumes/` except the ones already covered by the database and upload restore steps
|
|
6. Starts Traefik and the production stack again
|
|
|
|
If the backup contains raw archives for volumes used by other stacks such as GitLab or Portainer, stop those containers before running restore. The script will refuse to overwrite a volume that is currently attached to a running container.
|
|
|
|
Before running restore on a live server, take a fresh backup first.
|
|
|
|
#### Stop the stack
|
|
|
|
```bash
|
|
# Stop containers but keep volumes (data is preserved)
|
|
npm run docker:prod:down
|
|
|
|
# Stop and delete all data (destructive — irreversible)
|
|
docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml down -v
|
|
```
|
|
|
|
#### pgManage (DB admin UI)
|
|
|
|
pgManage is available at `https://pgmanage.rentaldrivego.ma`. To connect to the production database, add a connection inside pgManage with:
|
|
|
|
- **Host:** `localhost`
|
|
- **Port:** `5432`
|
|
- **Database:** `rentaldrivego`
|
|
- **Username:** `postgres`
|
|
- **Password:** value of `POSTGRES_PASSWORD` from `.env.docker.production`
|
|
|
|
#### Portainer
|
|
|
|
Portainer is available at `https://portainer.rentaldrivego.ma`.
|
|
|
|
Use this command to deploy or update it:
|
|
|
|
```bash
|
|
npm run docker:prod:start:portainer
|
|
```
|
|
|
|
### Notes
|
|
|
|
- The production image builds the whole monorepo once, then each service overrides its runtime command.
|
|
- The dev compose file bind-mounts the repo and keeps `node_modules` in a named volume.
|
|
- `API_INTERNAL_URL` is used for server-side container-to-container calls, while `NEXT_PUBLIC_API_URL` is used by the browser.
|
|
- The Dockerfiles activate the repo's pinned `npm@10.5.0` with `corepack` before install so container builds do not depend on the npm version bundled with the base image.
|
|
- The dev compose stack stores Postgres data in `postgres_dev_data` and the bootstrap marker in `postgres_bootstrap_state`, so `up --build` does not reseed an existing local database.
|
|
- If you need database schema updates inside Docker, run:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml run --rm migrate
|
|
```
|
|
|
|
If a cached base image still fails during `npm ci`, refresh it and rebuild without cache:
|
|
|
|
```bash
|
|
docker pull node:20-bookworm
|
|
docker compose -f docker-compose.dev.yml build --no-cache dashboard
|
|
```
|