Intial commit for deployment script p2

This commit is contained in:
2026-05-04 23:23:02 +05:00
parent 724e6a8ecd
commit 40fe69ed65
20 changed files with 2008 additions and 2 deletions
+384
View File
@@ -0,0 +1,384 @@
# Deploying to Google Cloud Run
This folder contains everything needed to deploy the Htmx app to Google Cloud Run — completely isolated from the application code.
## Folder structure
```
GCR/
├── .env.example ← copy to .env and fill in your values
├── Dockerfile ← multi-stage AOT build (Linux/amd64)
├── entrypoint.sh ← maps Cloud Run's PORT var to ASP.NET Core
├── docker-compose.yml ← Cloud Run service definition (used by gcloud)
├── run-all.sh ← smart Linux runner (checks + prompts)
├── run-all.ps1 ← smart Windows runner (checks + prompts)
└── scripts/
├── 00-install-gcloud.sh / .ps1 ← install Google Cloud CLI
├── 01-login.sh / .ps1 ← authenticate + configure Docker
├── 02-setup-project.sh / .ps1 ← one-time GCP project setup
├── 03-create-secrets.sh / .ps1 ← manage MongoDB secret
└── 04-deploy.sh / .ps1 ← build, push, and deploy
```
`.sh` scripts are for **Linux**. `.ps1` scripts are for **Windows** (PowerShell 5.1+).
### One-command flow (recommended)
Instead of running each step manually, use the root runner. It checks each step,
shows completed items, and prompts to run only missing steps.
**Linux:**
```bash
bash GCR/run-all.sh
# non-interactive (auto-run missing steps):
bash GCR/run-all.sh --yes
```
**Windows:**
```powershell
.\GCR\run-all.ps1
# non-interactive (auto-run missing steps):
.\GCR\run-all.ps1 -Yes
```
## Security on Untrusted Machines
Do **not** run these scripts on machines you don't control unless absolutely necessary.
Why this matters:
1. The scripts grant project-level IAM roles to your user, including `roles/secretmanager.admin`.
2. `gcloud auth login` stores local credentials/tokens that can be reused if the machine is compromised.
3. Docker auth is configured for Artifact Registry and may persist in local Docker config.
4. A local `GCR/.env` file contains project identifiers and deployment metadata.
Minimum cleanup if you ever used a shared/untrusted machine:
1. Revoke IAM roles from your user account in the GCP project.
2. Revoke local gcloud credentials and clear config.
3. Remove Docker credential entries for Artifact Registry.
4. Delete local `GCR/.env` and any temporary files.
Example role cleanup (Linux/macOS shell):
```bash
USER_EMAIL="your-user@company.com"
PROJECT_ID="your-project-id"
for ROLE in \
roles/run.developer \
roles/artifactregistry.writer \
roles/iam.serviceAccountUser \
roles/secretmanager.admin \
roles/secretmanager.secretAccessor \
roles/secretmanager.secretVersionAdder; do
gcloud projects remove-iam-policy-binding "$PROJECT_ID" \
--member="user:$USER_EMAIL" \
--role="$ROLE" \
--quiet
done
```
Credential cleanup:
```bash
gcloud auth revoke --all
gcloud config configurations delete default --quiet || true
```
Credential cleanup (Windows PowerShell):
```powershell
gcloud auth revoke --all
gcloud config configurations delete default --quiet
```
Prefer a dedicated personal/admin workstation, or use a tightly scoped CI service account instead of broad user credentials.
---
## Step 0 — Configure your .env file
Copy the example and fill it in:
```bash
# Linux
cp GCR/.env.example GCR/.env
```
```powershell
# Windows
Copy-Item GCR\.env.example GCR\.env
```
Open `GCR/.env` in any editor and set:
| Variable | Description | Example |
|---|---|---|
| `GCP_PROJECT_ID` | Your GCP project ID | `my-htmx-project` |
| `GCP_REGION` | Cloud Run region | `us-central1` |
| `GCP_REPOSITORY` | Artifact Registry repo name | `htmx` |
| `SERVICE_NAME` | Cloud Run service name | `htmx-app` |
| `MONGODB_DATABASE_NAME` | Database name | `HtmxAppDb` |
> **Security note:** `GCR/.env` is gitignored. Never commit it.
> **MongoDB note:** The app connects to MongoDB at startup. Cloud Run containers do not have access to `localhost:27017` — use MongoDB Atlas (cloud-hosted) or a MongoDB instance reachable over the internet/VPC.
> **Secrets note:** The MongoDB connection string is **not stored in .env**. It's stored securely in Google Cloud Secret Manager. See **Step 4** below.
---
## Step 1 — Install the Google Cloud CLI
Run **once** on a new machine.
**Linux:**
```bash
bash GCR/scripts/00-install-gcloud.sh
```
**Windows** (PowerShell, run as Administrator):
```powershell
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # first time only
.\GCR\scripts\00-install-gcloud.ps1
```
After installation, open a new terminal and verify:
```
gcloud version
```
---
## Step 2 — Log in
Authenticates your machine to GCP and configures Docker to push to Artifact Registry.
**Linux:**
```bash
bash GCR/scripts/01-login.sh
```
**Windows:**
```powershell
.\GCR\scripts\01-login.ps1
```
A browser window opens for Google sign-in. Use the account that owns or has access to your GCP project.
---
## Step 3 — Set up the GCP project (one time)
Enables APIs, creates the Artifact Registry repository, and grants your account the required IAM roles. If billing is not yet linked, the script prompts you to choose a billing account.
**Linux:**
```bash
bash GCR/scripts/02-setup-project.sh
```
**Windows:**
```powershell
.\GCR\scripts\02-setup-project.ps1
```
This is **safe to re-run** — all operations are idempotent.
### What it enables
| API | Purpose |
|---|---|
| `run.googleapis.com` | Cloud Run service |
| `artifactregistry.googleapis.com` | Docker image storage |
| `secretmanager.googleapis.com` | Available for future use |
| `cloudresourcemanager.googleapis.com` | IAM and project management |
### What IAM roles it grants (to your account)
| Role | Purpose |
|---|---|
| `roles/run.developer` | Deploy and manage Cloud Run services |
| `roles/artifactregistry.writer` | Push container images |
| `roles/iam.serviceAccountUser` | Run the service under a service account |
| `roles/secretmanager.admin` | Create/manage secrets and IAM policies (includes `secretmanager.secrets.create`) |
| `roles/secretmanager.secretAccessor` | Read secret payloads (for validation/access workflows) |
| `roles/secretmanager.secretVersionAdder` | Add/set new secret versions (rotate values safely) |
---
## Step 4 — Create secrets in Google Cloud Secret Manager
Store the MongoDB connection string securely:
**Linux:**
```bash
bash GCR/scripts/03-create-secrets.sh
```
**Windows:**
```powershell
.\GCR\scripts\03-create-secrets.ps1
```
The script prompts for your MongoDB connection string, creates the secret in Secret Manager, and grants Cloud Run permission to access it. The secret is referenced by name (`mongodb-connection-string`) in the deploy script — never stored in .env.
This is a **one-time setup**. Re-run only if you need to **update** the MongoDB connection string.
---
## Step 5 — Deploy
Builds the Docker image, pushes it to Artifact Registry, and deploys to Cloud Run.
If secrets are missing, the deploy script now performs a pre-check and prompts to run
the secrets setup script before continuing.
**Linux:**
```bash
bash GCR/scripts/04-deploy.sh
# or with a custom image tag:
bash GCR/scripts/04-deploy.sh v1.0.0
```
**Windows:**
```powershell
.\GCR\scripts\04-deploy.ps1
# or with a custom image tag:
.\GCR\scripts\04-deploy.ps1 -Tag v1.0.0
```
The script:
1. Checks for (or generates) `Htmx.ApiDemo/package-lock.json`
2. Builds the Docker image from the repo root using `GCR/Dockerfile`
3. Pushes the image to Artifact Registry
4. Deploys to Cloud Run using `GCR/docker-compose.yml`
5. Opens the service to public access (no authentication required)
6. Prints the live service URL
By default the image tag is the short git commit SHA (e.g. `a3f4b7c`). A timestamp is used if the directory is not a git repo.
---
## How configuration reaches the app
The app reads configuration from environment variables. Cloud Run injects them at container startup — no config files needed in the image.
| Environment variable | Maps to | Set by |
|---|---|---|
| `ConnectionStrings__DefaultConnection` | `appsettings.json``ConnectionStrings.DefaultConnection` | Secret Manager (via `--set-secrets`) → deploy script |
| `MongoDbName` | `appsettings.json``MongoDbName` | `GCR/.env` → deploy script → docker-compose.yml |
| `ASPNETCORE_ENVIRONMENT` | ASP.NET Core environment | `docker-compose.yml` (hardcoded `Production`) |
| `PORT` | Listening port | Injected by Cloud Run (default `8080`) |
**Secret Manager workflow:**
- Step 4 stores the MongoDB connection string in Cloud Run Secret Manager
- Step 5 (deploy script) injects it via `gcloud run services update --set-secrets=...`
- The container never sees the raw connection string; Cloud Run mounts it as an env var at runtime
- Each time you update the secret, Cloud Run automatically uses the latest version
The `GCR/entrypoint.sh` script translates Cloud Run's `PORT` variable into `ASPNETCORE_HTTP_PORTS` at container startup, since ASP.NET Core does not read `PORT` directly.
---
## Re-deploying after code changes
Just run Step 5 again. Each deployment gets a new image tag (git SHA), and Cloud Run creates a new immutable revision. Traffic is shifted to the new revision automatically.
---
## Updating configuration
### Non-sensitive config (MONGODB_DATABASE_NAME, etc.)
To change a non-sensitive value without rebuilding:
**Linux:**
```bash
source GCR/.env
gcloud run services update $SERVICE_NAME \
--region=$GCP_REGION \
--update-env-vars "MongoDbName=NewDatabaseName"
```
**Windows:**
```powershell
gcloud run services update htmx-app `
--region=us-central1 `
--update-env-vars "MongoDbName=NewDatabaseName"
```
### Sensitive config (MongoDB connection string)
To update the MongoDB connection string:
**Linux:**
```bash
source GCR/.env
# Read from stdin (paste the connection string and press Ctrl+D):
gcloud secrets versions add mongodb-connection-string \
--data-file=- \
--project=$GCP_PROJECT_ID
```
**Windows:**
```powershell
# Use a temp file to avoid adding trailing newlines to the secret
$connectionString = Read-Host -AsSecureString "Enter MongoDB connection string"
$connectionStringPlainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($connectionString))
$TempFile = [System.IO.Path]::GetTempFileName()
try {
[System.IO.File]::WriteAllText($TempFile, $connectionStringPlainText, [System.Text.Encoding]::UTF8)
gcloud secrets versions add mongodb-connection-string --data-file=$TempFile --project=your-project-id
} finally {
Remove-Item $TempFile -Force -ErrorAction SilentlyContinue
}
```
Or re-run the creation script:
**Linux:**
```bash
bash GCR/scripts/03-create-secrets.sh
```
**Windows:**
```powershell
.\GCR\scripts\03-create-secrets.ps1
```
Cloud Run automatically uses the latest secret version on the next container start.
---
## Troubleshooting
### Docker build fails on `npm ci`
`npm ci` requires `Htmx.ApiDemo/package-lock.json` to exist. Generate it locally:
```bash
cd Htmx.ApiDemo && npm install
```
Then commit `package-lock.json` to the repository.
### `gcloud: command not found` after install
Close and reopen your terminal. The installer adds `gcloud` to `PATH`, but the current shell session won't see it until restarted.
### `PERMISSION_DENIED` errors during deploy
Run `02-setup-project` again — it grants the required IAM roles. It is safe to re-run.
### Cloud Run container crashes on startup
View logs in the GCP console (Cloud Run → your service → Logs tab), or:
```bash
gcloud run services logs read $SERVICE_NAME --region=$GCP_REGION --limit=50
```
The most common causes:
- MongoDB connection string is wrong or unreachable from Cloud Run
- `ASPNETCORE_ENVIRONMENT` is `Production` but `appsettings.Production.json` overrides something unexpectedly
### Service URL returns 404 for all routes
The service is running but no route matched. Confirm the app started correctly by checking logs for `Now listening on`.