# eCom01 Production Deploy

Production runs as an isolated Docker Compose project named `ecom01` under:

```text
/var/www/stacks/ecom01/repo
```

It uses the existing server Traefik container through the external Docker
network `traefik-public`. Do not create another Traefik instance for this
project.

## Domains

- Main site: `https://www.ecom01.rs`
- Apex redirect: `https://ecom01.rs` -> `https://www.ecom01.rs`
- Admin/backend: `https://admin.ecom01.rs`
- Media CDN: `https://cdn.ecom01.rs`

## Server Env Files

The deploy script expects these real env files on the server. They are not
committed to git:

```text
/var/www/stacks/ecom01/repo/.env.production
/var/www/stacks/ecom01/repo/admin/.env
/var/www/stacks/ecom01/repo/frontend/.env.production
```

Templates are committed as:

```text
.env.production.example
admin/.env.production.example
frontend/.env.production.example
```

Required values to fill on the server:

- `APP_KEY`
- `DB_PASSWORD`
- `DB_ROOT_PASSWORD`
- `MAIL_*`
- `PUMBLE_WEBHOOK_URL`
- `NEXT_PUBLIC_GA4_ID`
- `NEXT_PUBLIC_CLARITY_ID`
- `LIVEWIRE_ASSET_URL`

## Containers and Volumes

The production stack owns only these containers:

```text
ecom01_frontend
ecom01_backend
ecom01_nginx
ecom01_db
ecom01_redis
```

The production stack owns only these Docker volumes:

```text
ecom01_mysql_data
ecom01_redis_data
```

Do not run `docker system prune`, do not remove shared networks, and do not
restart unrelated stacks.

## Manual Deploy

From the server, when the server has Git access:

```bash
cd /var/www/stacks/ecom01/repo
./scripts/deploy.sh
```

From another machine with SSH access:

```bash
ssh ecom01docker@172.24.10.26 \
  "DEPLOY_ROOT=/var/www/stacks/ecom01 DEPLOY_BRANCH=main bash -s" \
  < scripts/deploy.sh
```

GitLab CI does not require the server to have a GitLab deploy key. The deploy
job rsyncs the checked-out repository to `/var/www/stacks/ecom01/repo` while
preserving server-only env, storage, vendor and build output, then runs:

```bash
DEPLOY_SKIP_CHECKOUT=1 ./scripts/deploy.sh
```

The script records `docker ps`, backs up the existing ecom01 folder/database
when present, optionally pulls the current branch, builds only the `ecom01`
compose project, runs Laravel production commands, and leaves other Docker
projects untouched.

## Backups

Create a manual backup of the production database and static files:

```bash
cd /var/www/stacks/ecom01/repo
./scripts/backup.sh
```

From another machine with SSH access:

```bash
ssh ecom01docker@172.24.10.26 \
  'cd /var/www/stacks/ecom01/repo && ./scripts/backup.sh'
```

The backup script writes timestamped folders under:

```text
/var/www/stacks/ecom01/backups/
```

Each backup includes:

- `database.sql.gz` - MySQL dump with routines, triggers and events.
- `cdn-public.tar.gz` - uploaded files served by `https://cdn.ecom01.rs`.
- `frontend-public.tar.gz` - static files from `frontend/public`.
- `docker-ps.txt` and `manifest.txt` - container snapshot and backup metadata.

Old backups are removed after `BACKUP_RETENTION_DAYS`, defaulting to 14 days:

```bash
BACKUP_RETENTION_DAYS=30 ./scripts/backup.sh
```

## Performance and CDN

Uploaded CMS media is stored in `admin/storage/app/public` and served by the
Nginx CDN virtual host at `https://cdn.ecom01.rs`. Static CDN assets use
long-lived immutable cache headers, so replace uploaded assets with unique file
names instead of overwriting an existing file when the browser must fetch a new
version immediately.

The Next.js frontend runs in standalone production mode and uses AVIF/WebP image
optimization with a long image cache TTL. Public CMS API reads are cached by
Next.js revalidation; tune the interval with:

```dotenv
NEXT_PUBLIC_API_REVALIDATE_SECONDS=300
```

Production PHP uses OPcache with timestamp validation disabled inside the
container, and deploy rebuilds/restarts only the `ecom01` PHP container when code
changes. Nginx also enables gzip and open-file caching for the admin and CDN
virtual hosts.

## Database Transfer

Create the local dump without committing it:

```bash
mkdir -p .deploy
docker compose exec -T mysql sh -lc \
  'mysqldump --single-transaction --quick --default-character-set=utf8mb4 -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' \
  | gzip -c > .deploy/ecom01-local.sql.gz
```

Copy it to the server:

```bash
rsync -av .deploy/ecom01-local.sql.gz ecom01docker@172.24.10.26:/var/www/stacks/ecom01/dumps/
```

Import it into the production DB container:

```bash
ssh ecom01docker@172.24.10.26
cd /var/www/stacks/ecom01/repo
gunzip -c /var/www/stacks/ecom01/dumps/ecom01-local.sql.gz | \
  docker compose -p ecom01 --env-file .env.production -f docker-compose.prod.yml exec -T db \
  sh -lc 'mysql --default-character-set=utf8mb4 -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"'
```

If production already has content, back it up first:

```bash
docker compose -p ecom01 --env-file .env.production -f docker-compose.prod.yml exec -T db \
  sh -lc 'mysqldump --single-transaction --quick -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' \
  | gzip -c > /var/www/stacks/ecom01/backups/db-before-manual-import.sql.gz
```

## Post Deploy Checks

```bash
docker ps
docker compose -p ecom01 --env-file .env.production -f docker-compose.prod.yml ps
docker compose -p ecom01 --env-file .env.production -f docker-compose.prod.yml logs --tail=100 php nginx frontend
curl -I https://www.ecom01.rs
curl -I https://ecom01.rs
curl -I https://admin.ecom01.rs/health
curl -I https://cdn.ecom01.rs
```

Also check that `elmag`, `repo-*`/WMS, and `traefik` containers are still up.
