# Cloud Cost Portal Container Deployment

The portal ships as two services (FastAPI backend and React frontend). You must provide a MySQL instance that already contains the billing datasets populated by the exporters. This document covers how to build images, run a single-host stack for testing against an external database, and roll out updates.

## Prerequisites
- Docker Engine 24+ and Docker Compose Plugin 2.20+.
- Access to a shell on the host that can reach your MySQL/MySQL-compatible target if you do not use the bundled database.

## Build Container Images
Run the following from the repository root:

```bash
# Backend image
docker build -t cloud-cost-portal-backend:latest portal/backend

# Frontend image
docker build -t cloud-cost-portal-frontend:latest portal/frontend
```

### Build Multi-Architecture Images (amd64 + arm64)
Use Docker Buildx when you need to publish images that support both x86_64 and Apple Silicon/Graviton targets:

```bash
# create (once) and select a builder that supports multi-arch
docker buildx create --name portal-multiarch --driver docker-container --use

# backend: build and push amd64/arm64 in a single command
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag hub.pingcap.net/cloud-costs-management/cloud-cost-portal-backend:<version> \
  --push \
  portal/backend

# frontend: same flow
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag hub.pingcap.net/cloud-costs-management/cloud-cost-portal-frontend:<version> \
  --push \
  portal/frontend
```

Skip `--push` if you only want local multi-arch cache manifests; add `--load` to import the native architecture back into the local Docker engine for testing.

Tag the images before pushing to your registry, for example:

```bash
docker tag cloud-cost-portal-backend:latest <registry>/cloud-cost-portal-backend:<version>
docker push <registry>/cloud-cost-portal-backend:<version>

docker tag cloud-cost-portal-frontend:latest <registry>/cloud-cost-portal-frontend:<version>
docker push <registry>/cloud-cost-portal-frontend:<version>
```

## Local Single-Host Deployment
The repository contains `portal/docker-compose.yml` for a lightweight stack (frontend + backend) that connects to an external MySQL service.

1. Set the database connection string so Docker Compose can pass it to the backend:

   ```bash
   export DATABASE_URL="mysql+pymysql://billing_reader:password@<mysql-host>:3306/cloud_billing?charset=utf8mb4"
   ```

   Adjust credentials, host, port, and database name to match your environment.

2. Start the services from the `portal/` directory:

   ```bash
   cd portal
   docker compose up --build -d
   ```

   Compose builds the images and launches the stack. The backend reads `DATABASE_URL` and connects directly to your MySQL instance.

3. Verify services:
   - Frontend: http://127.0.0.1:3000
   - Backend API & docs: http://127.0.0.1:8000/docs
   - Database: connect directly to your external MySQL endpoint to confirm the schema is present.

4. Stop the stack when finished:

   ```bash
   docker compose down
   ```

   The stack only contains stateless services, so no persistent volumes are created.

## Updating Running Deployments
When you have new changes:

1. Build or pull the updated images (see the build section above).
2. If you published new tags, update the `image:` references in `portal/docker-compose.yml` (replace the existing `build:` blocks) or set `COMPOSE_PROFILES` to skip builds and rely on prebuilt images.
3. Apply the update:

   ```bash
   cd portal
   docker compose pull   # only if you switched to registry-hosted images
   docker compose up -d --build backend frontend
   ```

4. Confirm the containers picked up the new image digests with `docker compose ps`.

## Notes for Non-Compose Deployments
- The frontend Nginx layer proxies `/api/` requests to `http://backend:8000`. Adjust `portal/frontend/nginx.conf` if your upstream is published under a different hostname or path.
- The backend expects `DATABASE_URL` in SQLAlchemy format. Provide a user with read access to the billing tables populated by the AWS/GCP exporters. Apply any schema migrations (see `portal/backend/sql/schema.sql`) directly to your managed MySQL service.
- If your MySQL/TiDB endpoint enforces TLS, set `DATABASE_USE_TLS=true`. The container falls back to common system CA bundles (`/etc/ssl/certs/ca-certificates.crt` or `/etc/ssl/cert.pem`), but you can override with `DATABASE_SSL_CA=/custom/ca.pem` if needed. Optional variables: `DATABASE_SSL_CERT` and `DATABASE_SSL_KEY` for mutual TLS, `DATABASE_SSL_VERIFY=false` if hostname verification must be disabled (not recommended).
- Remember to configure TLS, centralized logging, and secrets management before promoting beyond development or staging environments.
