Skip to content

Commit aa7e628

Browse files
committed
docs: clarify Azure cold starts and deployment check
1 parent 10fb996 commit aa7e628

3 files changed

Lines changed: 43 additions & 8 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ This monorepository contains three live cloud applications:
3636

3737
All applications run in **Microsoft Azure Container Apps** on the **Azure for Students** plan using the Serverless Consumption Tier (Scale-to-Zero):
3838

39+
> [!NOTE]
40+
> **Cold-start behavior:** these demos can scale to zero after being idle. The
41+
> first visit then has to wake a new Azure replica, so the page may load slowly
42+
> or briefly appear unavailable. Wait a few seconds and reload once. Persistent
43+
> failures should be checked in Azure revision and container logs. Keeping
44+
> `minReplicas = 1` avoids scale-to-zero wakeups at the cost of idle usage. See
45+
> the [Azure Container Apps deployment guide](docs/AZURE_CONTAINER_APPS.md#scale-to-zero-cold-start-notice).
46+
3947
| Application | Live Public URL | Key Features | Admin / Cockpit |
4048
| :--- | :--- | :--- | :--- |
4149
| **🌐 Rullst Showcase** | [showcase.rullst.win](https://showcase.rullst.win) | 5 Web Paradigms, WAF Defense, LiveView, Active Record, Groq AI Copilot | 🛡️ **Nexus Admin:** [/nexus](https://rullst-showcase.redpond-24d9228d.eastus.azurecontainerapps.io/nexus)<br>🚀 **Studio:** [/studio](https://rullst-showcase.redpond-24d9228d.eastus.azurecontainerapps.io/studio)<br>*(Administrator credentials required)*<br>🤖 **Groq AI:** GPT-OSS 120B & Prompt Shield<br>📖 [Showcase Guide](docs/showcase-nexus-studio-ai-guide.md) |

docs/AZURE_CONTAINER_APPS.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ Why is Rullst the ultimate framework for serverless and cost-conscious cloud dep
1111
| Metric | 🦀 **Rullst (Rust)** |**Spring Boot (Java)** | 🐍 **Django / FastAPI (Python)** | 🐘 **Laravel (PHP)** |
1212
| :--- | :--- | :--- | :--- | :--- |
1313
| **Idle Memory (RAM)** | **~15–25 MB** | **~350–600 MB** (JVM overhead) | **~120–200 MB** (Gunicorn/Uvicorn) | **~150–250 MB** (PHP-FPM/Octane) |
14-
| **Cold-Start Wakeup** | **< 50 ms** (Instant) | **8–25 seconds** (JVM class loading) | **4–10 seconds** (Module imports) | **3–8 seconds** (Bootstrap & autoloader) |
15-
| **Scale-to-Zero Viability** | 🟢 **Perfect**: Users never notice a delay | 🔴 **Poor**: Frequent timeouts during wake-up | 🟡 **Moderate**: Noticeable cold lag | 🟡 **Moderate**: Noticeable cold lag |
14+
| **Application Process Startup** | **< 50 ms** in the measured binary | **8–25 seconds** (JVM class loading) | **4–10 seconds** (Module imports) | **3–8 seconds** (Bootstrap & autoloader) |
15+
| **Scale-to-Zero Viability** | 🟢 **Good for cost-sensitive demos**, with a user-visible platform cold start | 🔴 **Poor**: Frequent timeouts during wake-up | 🟡 **Moderate**: Noticeable cold lag | 🟡 **Moderate**: Noticeable cold lag |
1616
| **Minimum ACA Size Required** | **0.25 vCPU / 0.5 GiB** | **1.0 vCPU / 2.0 GiB** (Minimum viable) | **0.5 vCPU / 1.0 GiB** | **0.5 vCPU / 1.0 GiB** |
1717
| **Apps Fitting in Free Tier** | 🟢 **10–15 apps** fit comfortably | 🔴 **0–1 app** (exceeds free memory immediately) | 🟡 **1–2 apps** max | 🟡 **1–2 apps** max |
1818
| **Throughput (req/s per core)** | **~80,000+** (Zero-cost Tokio async) | **~15,000–25,000** | **~2,500–5,000** | **~3,000–6,000** |
1919
| **Estimated Monthly Cost** | **$0.00** (Full coverage under free grant) | **$25.00 – $45.00/mo** | **$12.00 – $20.00/mo** | **$12.00 – $20.00/mo** |
2020

2121
### 💡 Why Rullst Saves Over 90% in Cloud Infrastructure:
2222
1. **No Garbage Collection (GC) or JIT Runtime:** Rullst compiles directly to native machine code. There is no JVM or Python interpreter consuming hundreds of megabytes just to stay idle.
23-
2. **True Scale-to-Zero:** Because a Rullst container boots from zero in less than 50 milliseconds, you can aggressively configure replicas to drop to `0` when idle. When a visitor arrives, Azure wakes up the container in ~1 second, completely imperceptible to human browsing.
23+
2. **Scale-to-Zero with a Tradeoff:** The Rullst process starts quickly, but an Azure cold start also includes image availability, resource provisioning, container creation, initialization and health probes. The first request after an idle period can therefore be visibly slower and may briefly look unavailable. This is not guaranteed to be a one-second or imperceptible transition.
2424
3. **Massive Density:** On a single server or cloud plan, you can run 10x more Rullst microservices or tenant sites than equivalent Spring Boot or Django instances.
2525

2626
---
@@ -32,12 +32,37 @@ Why is Rullst the ultimate framework for serverless and cost-conscious cloud dep
3232
| **Idle Cost** | 💰 Charges **24/7**, even with 0 visitors (~$10–$15/mo). Depletes student credits in a few months. | 🟢 **$0.00** when idle (**Scale-to-Zero**). Replicas automatically shut down when traffic stops. | Rullst uses zero idle CPU/RAM when scaled to zero. |
3333
| **Monthly Free Grant** | ❌ Limited or expired free VM hours. | 🎁 **180,000 vCPU-seconds**, **360,000 GiB-seconds**, and **2,000,000 requests/month FREE**. | Your $100 credit lasts the **full 12 months** without depletion. |
3434
| **Regional Availability** | ⚠️ Frequent allocation failures in student accounts (*"QuotaExceeded"* / *"Regional capacity exhausted"* in `East US`, etc.). | ✅ Runs on Microsoft's elastic serverless fleet. No dedicated hardware reservation required. | Deploys reliably across regions without quota friction. |
35-
| **Cold-Start Latency** | N/A (always on). | ~1–2 seconds to wake up from zero. | Pure native Rust binary boots in **< 50ms**. Cold starts are imperceptible compared to Node.js/Python (10–30s). |
35+
| **Cold-Start Latency** | N/A (always on). | Variable and user-visible when waking from zero; it includes platform work beyond process startup. | The native Rust process starts quickly, but cannot eliminate Azure provisioning, image and probe latency. |
3636
| **OS Maintenance** | 🛠️ Manual: `apt upgrade`, SSH keys, Linux kernel patches, UFW firewall, systemd services. | 🛡️ Fully managed by Azure. Zero OS patching or infrastructure burden. | Focus purely on your Rust application and business logic. |
3737
| **TLS / HTTPS** | 🔐 Manual Let's Encrypt certbot setup and renewal cron jobs. | 🔒 **Automatic managed TLS/SSL certificate** with global Anycast routing. | Instant, zero-config HTTPS with custom domain support. |
3838

3939
---
4040

41+
## Scale-to-Zero Cold-Start Notice
42+
43+
These examples use **minimum replicas = 0** to preserve the Azure for Students
44+
budget. After an idle period, the next request starts a cold container. During
45+
that interval the browser may wait longer than usual or briefly show a gateway,
46+
connection or apparently broken-link error. Wait a few seconds and reload once.
47+
If the site remains unavailable, inspect the active revision, replica status,
48+
startup/readiness probes and container logs instead of assuming it is only cold.
49+
50+
The Rullst binary's process startup time is only one component of the end-to-end
51+
cold start. Microsoft documents that scale-to-zero makes the next request trigger
52+
image/resource provisioning and application startup, and recommends client-side
53+
accommodations. See [Reducing cold-start time on Azure Container Apps](https://learn.microsoft.com/azure/container-apps/cold-start).
54+
55+
For a user-facing service where first-request latency matters more than idle
56+
cost, configure **minimum replicas = 1**. This keeps an instance available but
57+
can incur idle charges. See [Scaling in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/scale-app).
58+
59+
This cold-start behavior is separate from revision rollout. In single-revision
60+
mode, Azure keeps the previous revision serving traffic until the new revision
61+
passes its startup and readiness checks; an app configured with zero minimum
62+
replicas can still cold-start later after becoming idle.
63+
64+
---
65+
4166
## 🔒 Security Invariants & Production Defaults (Fail-Closed Architecture)
4267

4368
When deploying Rullst applications to production, the framework enforces **Secure-by-Default (Fail-Closed)** invariants:
@@ -89,7 +114,7 @@ In the **Azure Portal**:
89114
- `PORT`: `3000`
90115
- `APP_ENV`: `production`
91116
- `NEXUS_ADMIN_USERNAME`: `rullst_admin`
92-
- `NEXUS_ADMIN_PASSWORD`: `SovereignRullst2026!Key` (16+ characters)
117+
- `NEXUS_ADMIN_PASSWORD`: reference a deployment secret containing 16+ characters; never commit or publish the value
93118
10. **Scale Rules (Scale-to-Zero):**
94119
- Set **Min replicas:** `0`
95120
- Set **Max replicas:** `1` (or scale dynamically with traffic)
@@ -113,13 +138,14 @@ az containerapp create \
113138
--max-replicas 1 \
114139
--cpu 0.25 \
115140
--memory 0.5Gi \
141+
--secrets nexus-admin-password="$NEXUS_ADMIN_PASSWORD" \
116142
--env-vars \
117143
HOST=0.0.0.0 \
118144
PORT=3000 \
119145
RULLST_ENV=production \
120146
DATABASE_URL="sqlite:///app/db.sqlite?mode=rwc" \
121147
NEXUS_ADMIN_USERNAME=admin \
122-
NEXUS_ADMIN_PASSWORD="SovereignPortfolio2026!"
148+
NEXUS_ADMIN_PASSWORD=secretref:nexus-admin-password
123149
```
124150

125151
#### Via GitHub Actions (Automated CI/CD):
@@ -132,4 +158,4 @@ Upon committing and pushing to the `main` branch:
132158

133159
## 📊 Monitoring & Telemetry
134160
- **Live Logs:** In Azure Portal, navigate to **Application > Containers > Console log stream** to view real-time Rust logs.
135-
- **Zero Downtime Revisions:** Azure maintains traffic zero-downtime routing across revisions (`rullst-showcase--0000001`, `rullst-portfolio--0000001`).
161+
- **Revision Rollout:** In single-revision mode, Azure keeps the previous healthy revision serving until the new revision passes startup and readiness checks. This does not remove later scale-to-zero cold starts.

scripts/verify-deployment.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ def request(path, message=None, authenticated=False, cross_origin=False):
8888
data = None
8989
if message is not None:
9090
headers.update({"Origin": "https://invalid.example" if cross_origin else origin,
91-
"X-Rullst-AI": "1", "Sec-Fetch-Site": "same-origin",
91+
"X-Rullst-AI": "1",
92+
"Sec-Fetch-Site": "cross-site" if cross_origin else "same-origin",
9293
"Content-Type": "application/x-www-form-urlencoded"})
9394
token = next((c.value for c in cookies if c.name == "rullst_csrf"), "")
9495
headers["X-CSRF-Token"] = token

0 commit comments

Comments
 (0)