## 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 ### 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: - marketplace: `http://localhost:3000` - dashboard: `http://localhost:3001` - admin: `http://localhost:3002` - 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 marketplace 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 ``` 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`. ### 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` | marketplace and public site | | `api.rentaldrivego.ma` | API | | `pgmanage.rentaldrivego.ma` | pgManage (DB admin) | #### 2. Install Docker and clone the repo ```bash # Install Docker (if not already installed) curl -fsSL https://get.docker.com | sh git clone 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) | 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 marketplace and public site. The dashboard and admin panel are routed under that same host at `/dashboard` and `/admin`. #### 5. Start Traefik Traefik must be running before the app stack so it can wire up routes at startup. ```bash docker compose -f traefik.yaml up -d ``` #### 6. Build and start the app stack ```bash npm run docker:prod:up ``` Or use the new 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:marketplace 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:all ``` Docker will: 1. Build the monorepo image 2. Start all app services (`api`, `marketplace`, `dashboard`, `admin`, `pgmanage`) Traefik automatically picks up the containers and provisions TLS certificates. Services are live at their `https://` URLs within ~30 seconds. 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. #### Updating after a code change Pull the latest code and rebuild only the changed service: ```bash git pull docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml up --build -d --no-deps # e.g. to redeploy only the API: docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml up --build -d --no-deps api ``` To rebuild everything: ```bash npm run docker:prod:up ``` #### Apply database migrations without downtime ```bash docker compose -p rentaldrivego-prod --env-file .env.docker.production -f docker-compose.production.yml run --rm api npm run db:deploy ``` #### 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` ### 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 ```