When everyone's a developer,
they need somewhere to ship.
V2C is a secure route from vibe-coded app to your own Azure, plugged into your Entra identity, your OpenTofu state, and your enterprise AI gateway from day one.
A side project, not for sale. Read the engineering notes
The handoff
Give V2C one subscription.
It builds the rest.
The onboarding wizard walks you through creating a service principal and picking an Azure subscription. From there, V2C provisions the entire landing zone for you, no Terraform to run, no kubectl to install, no console tabs to juggle.
Everything V2C stands up lives inside your subscription, under resource-group tags you can audit. Nothing is shared between tenants. Nothing leaves your Azure estate.
Prefer to inspect before you adopt? V2C can export the whole landing zone as an OpenTofu / Terraform HCL bundle, with import blocks for every resource, non-destructive, one-way, safe to commit.
Resource group, VNet, NSG
A scoped landing zone, not a flat estate
Two AKS clusters
v2c-dev and v2c-prod, private, workload identity enabled
Azure Container Registry
Images built in-cluster by BuildKit, pushed here
Key Vault
Per-app secrets, injected as env vars at pod startup
API Management (optional)
AI gateway, per-project subscriptions
DNS + wildcard TLS
Azure DNS zone + Let's Encrypt wildcard, fanned out
Workload identities
Per-app, federated to the app's service account
MCP server · hosted alongside V2C
Deploy to private infrastructure
straight from your coding agent.
V2C runs an MCP server at /api/mcp. When a user finishes vibe-coding their app and says “put this in V2C”, the agent picks up four tools and drives the whole thing from inside the conversation. No portal tab. No form to fill. No copy-paste of project IDs. The agent confirms the name, owning team, and data sensitivity with the user, calls v2c_create_project, and monitors until the preview URL is live.
v2c_create_project
Creates the project in your tenant. Confirms with the user before committing via a dryRun pattern.
v2c_get_project_status
Phase, build, deploy, db / blob state, latest log. The agent polls until READY.
v2c_list_projects
Past projects so the agent can avoid duplicate names or pick one to monitor.
v2c_get_deploy_logs
Recent deploy attempts and their errors when something goes wrong.
Auth is your existing V2C API key. Mint one in the admin panel, paste it into Claude Desktop / Code / Cursor's MCP config, you're live. Every tool call lands in V2C's audit log under your email.
you
Looks great. Let's put this in V2C.
agent
Confirming: name “sales-copilot”, owning team Sales Engineering, data sensitivity restricted (uses customer records). OK to create?
you
Yep, ship it.
tool
v2c_create_project · created · id=clx7…
tool
v2c_get_project_status · phase=PREVIEW · build=BUILDING
agent
Project created. Building now (~3 min). I'll let you know when the preview URL is live.
AGENTS.md · generated per project
The coding agent gets
the rails it needs
on day one.
V2C writes an AGENTS.md into every project repo. It's generated dynamically, reflecting which platform features (APIM, health checks, tests, secrets) are actually enabled for your organisation, so the coding agent reads accurate rules, not generic boilerplate.
APIM integration
Exact env vars and call signature for your gateway
Availability tests
Contract for .v2c/tests.json and the health endpoint
Runtime constraints
Read-only filesystem, non-root UID, port 3000
What not to do
No hardcoded keys, no direct provider SDKs, no manual .v2c/ edits
# V2C Platform: AI Agent Instructions
This file is managed by V2C. It tells AI coding assistants how to work
with this project within the V2C platform.
---
## AI Model Access via API Management
### Step 1: Signal to V2C that this project needs AI access
Create (or update) .v2c/config.json in the repository root:
{ "apimAccess": true }
### V2C provisioning boundary
When apimAccess: true is set and the project is deployed, V2C automatically
provisions:
1. A per-project APIM subscription, a dedicated subscription key scoped
to this app only.
2. The environment variables listed in Step 2, injected into every pod
at deploy time.
3. The APIM backend wiring, including the upstream provider's API key,
attached by an APIM set-header policy at the gateway. The provider
key never reaches your container.
### Step 2: Use the injected environment variables
APIM_GATEWAY_URL gateway base URL
APIM_DEFAULT_PATH full URL to the chat/completions endpoint
APIM_DEFAULT_MODEL model name to pass in the request body
APIM_SUBSCRIPTION_KEY per-project key, the only auth your app needs
### Step 3: Call APIM
Authentication is the subscription key alone. Do not send an Authorization
header, and do not fetch an Azure AD / managed-identity token.
---
## Automated Availability Tests
V2C runs HTTP tests after every deployment. Maintain .v2c/tests.json:
[
{ "name": "Health check", "path": "/health", "expect": 200 },
{ "name": "Homepage", "path": "/", "expect": 200 }
]
Every app must expose GET /health returning 200.
---
## General Platform Rules
· Listen on port 3000 (PORT env var is set automatically)
· Do not write outside /tmp, root filesystem is read-only
· Container runs as non-root (UID 10001)
· Secrets are injected as env vars, never read from files or hardcode
· The .v2c/ directory is reserved, do not modify it manuallyAI access · under your enterprise agreement
Your enterprise AI,
embedded per app.
V2C plumbs apps into your Azure API Management gateway. Every application gets its own APIM subscription, so you get usage attribution per app, can throttle or revoke one without touching the others, and keep every request inside the data-retention and data-residency posture you've already negotiated.
No APIM yet? V2C provisions it for you. On the subscription you delegate, V2C can stand up an APIM instance, wire in your model providers, and publish per-app subscriptions, without you leaving the portal.
What the app gets at deploy time
injected env vars
✓ Per-app subscription
One APIM subscription per project, revoke, throttle, attribute
✓ Your AI contracts
OpenAI, Anthropic, Azure OpenAI, used under the enterprise agreement you already negotiated
✓ Zero key exposure
Provider API keys never leave the APIM backend
✓ Audit trail
Every call logged with the app's identity at the gateway
Usage, broken out per app
AI usage by app · today
via APIM| App | Model | Calls | Tokens |
|---|---|---|---|
| Expense Categoriser | gpt-4o | 143 | 28,420 |
| HR Policy Chatbot | gpt-4o | 89 | 15,230 |
| Contract Reviewer | gpt-4o | 34 | 41,180 |
| Support Triage | gpt-4o | 512 | 8,940 |
The business user gets an AI-powered app.
Built under your enterprise agreement. No API key was ever seen by a developer. IT has full visibility. The app is live in your cloud.
PostgreSQL · per-app, managed identity, no passwords
The agent asks for a database.
V2C makes it appear.
The coding agent decides it needs persistent storage. It declares that need in .v2c/config.json. V2C stands up an Azure PostgreSQL database, wires it to the app's Managed Identity, and injects the connection details at deploy time. Nobody on either side handles a password. Nobody on either side opens the Azure portal.
AGENTS.md teaches the pattern. Every project's AGENTS.md tells the coding agent that a Postgres database is available, how to opt in, the env vars it'll get, and a working @azure/identity snippet. The agent makes the call; V2C makes it real.
The agent declares the need
.v2c/config.json
{
"database": {
"engine": "postgresql",
"size": "small"
}
}## PostgreSQL Database
V2C can provision a dedicated, managed Azure PostgreSQL Flexible Server
for this project. It is optional, opt in only when the user asks for
persistent data (storing records, querying tables, etc.).
### How to enable
Create or update .v2c/config.json:
{ "database": { "engine": "postgresql", "size": "small" } }
### How authentication works
There is no password. The app's pod is federated to a User-Assigned
Managed Identity, registered as the AAD admin on this server. Fetch a
short-lived token via DefaultAzureCredential and use it as the Postgres
password. Refresh per connection.
### Example (Node.js with pg)
import { Pool } from "pg"
import { DefaultAzureCredential } from "@azure/identity"
const credential = new DefaultAzureCredential()
export const pool = new Pool({
host: process.env.PGHOST,
database: process.env.PGDATABASE,
user: process.env.PGUSER,
ssl: { rejectUnauthorized: true },
password: async () => {
const t = await credential.getToken(process.env.PG_AAD_SCOPE!)
return t!.token
},
})
### Rules
- Never include a password in the connection string
- Never disable TLS, sslmode=require is enforced
- Always fetch a fresh token via DefaultAzureCredentialV2C provisions, in your subscription
Dedicated workload resource group
v2c-{shortId}-{region}-rg, tagged for cleanup
Workload managed identity
Federated to the app's namespace ServiceAccount
PostgreSQL Flexible Server
AAD-only auth · TLS 1.2 · no admin password stored
Identity registered as AAD admin
Pod authenticates via DefaultAzureCredential
Database + firewall + connection details
PGHOST / PGUSER / PGDATABASE injected at deploy
injected env vars · at deploy time
✓ Zero stored credentials
AAD-only auth, workload identity is the only DB user. No password ever lands in V2C, Key Vault, or the pod.
✓ Tracked in OpenTofu
Database resources emitted into the workloads bundle alongside k8s + DNS, the same handoff story as the rest of the app.
The developer never picks an SKU. The end user never sees a connection string.
The agent decides a database is needed; V2C decides where it lives, how big it is, and how the app talks to it. Build doesn't unlock until the database is ready, so deploys never race the data layer.
Workload services · the agent's toolbox
Hand the agent a stocked toolbox.
Watch what it builds for your users.
Storage for files, vector search for meaning, scheduled work for anything that runs in the background. Each one is a single.v2c/config.json line, the agent picks the right tool for what your users asked for, V2C provisions it on the next deploy.
Blob Storage
File uploads & generated artefacts
When the agent decides the app needs to hold uploaded photos, generated PDFs, or AI-rendered artefacts, it declares a container. V2C provisions a private Storage Account, grants the workload identity Blob Data Contributor, and injects the URL, no keys, no SAS tokens, no API surface for the agent to fumble.
.v2c/config.json
{
"blob": {
"containers": [
"uploads",
"generated"
]
}
}- ✓One env var per container,
BLOB_CONTAINER_UPLOADS - ✓Shared-key access disabled at the SA level
- ✓Private blob endpoint (containers: private only)
pgvector
Vector search built into Postgres
The single most important primitive for the agent to build apps that amaze people. With pgvector available, the agent can ground every LLM answer in your own data, and your users get a chatbot that quotes their actual handbook, a search that understands what they meant, an assistant that remembers them across visits.
.v2c/config.json
{
"database": {
"engine": "postgresql",
"size": "small",
"extensions": ["pgvector"]
}
}- ✓RAG-ready: ground every LLM answer in your own data
- ✓HNSW + IVFFlat indexes for sub-second similarity at scale
- ✓Zero new Azure resources, extends the existing server
CronJobs
Scheduled work on the same pod
The agent declares scheduled work the same way it declares anything else. Each tick fires a Pod that inherits the app's image, env vars, and identity, same DB, same blob, same APIM. Nightly cleanup, hourly polling, weekly digest emails, no extra wiring.
.v2c/config.json
{
"cronJobs": [
{
"name": "nightly",
"schedule": "0 2 * * *",
"command": ["node",
"scripts/cleanup.js"]
}
]
}- ✓Inherits app pod template, same secrets, same identity
- ✓Concurrency forbidden, overlapping ticks skip cleanly
- ✓Reconciled per deploy: drop a job from config, V2C deletes it
The same agent-makes-the-call pattern. The coding agent reads AGENTS.md, understands what's available and when each is the right tool, declares the need in .v2c/config.json, and V2C does the rest. No agent-side credentials. No portal clicks. No platform tickets.
The agent's toolbox · vibe-coded apps that wow
Hand the agent the right primitives.
Watch your users' jaws drop.
A coding agent with only an LLM produces apps that hallucinate. A coding agent with an LLM plus a database, a vector store, a place to put files, and governed access to your warehouse data produces apps that feel like magic. V2C's job is to keep that toolbox stocked. Your agent picks the right tool. Your users wonder how it knew.
Your user asks
“Build me an app that lets people ask about our internal policies and get the right answer back.”
→Agent reaches for Snowflake
Pulls the policy registry, which policies exist, which are current, who owns each one. The data product was admin-approved, so the app already has scoped access.
→Agent reaches for pgvector
Finds the policy clauses most relevant to the question. Semantic match, not keyword grep, so “can I expense lunch” lands on the right travel-and-entertainment section even if those words aren't in the policy.
→Your user is amazed
Agent reaches for APIM with the registry metadata and the matching clauses both in context. The answer cites the policy name, the version, and the line. No hallucinations, no “check the wiki”.
What your agent will reach for these tools to build
One .v2c/config.json line per primitive, the agent writes it
A policy assistant that always cites the right page
Employees ask in natural language. The agent finds the current policy version in the registry, retrieves the clause, and quotes it back. No more hunting through SharePoint.
Snowflake · pgvector · APIM
A support tool that gets faster every week
Each ticket teaches it. Three weeks in, the agent is finding the resolution that worked last time before the human even reads the message.
APIM · pgvector
A search box your users will gush about
“Running shoes for slippery surfaces” finds the trail runners with grippy outsoles. The catalogue you already have, with the search it deserves.
APIM · pgvector
The agent decides when each tool is the right one. For a translation app it skips the vector store entirely. For an app that has to answer from your data, it reaches straight for pgvector. AGENTS.md teaches it the difference; you don't have to.
Snowflake · governed access by app identity
Your data warehouse,
opened safely to apps.
V2C connects to your Snowflake account once. Admins onboard tables and views as data products, or let V2C auto-discover them from a Snowflake tag your governance team controls. From there, every app that wants a product asks for it; an admin approves; V2C provisions per-app Snowflake roles tied to the app's Managed Identity. No static credentials. No shared service accounts. No data leaks via copy-paste.
AGENTS.md updates itself. As soon as a product is onboarded, manually or via tag, every project repo's AGENTS.md surfaces the table, columns, and column comments to the coding agent. The developer doesn't need to know your data dictionary; their agent reads it directly.
The developer asks for access in code
.v2c/config.json
{
"dataSources": [
{
"product": "v_top_customers",
"reason": "Build a churn dashboard"
}
]
}What V2C generates on approval
- ✓A Snowflake user with
LOGIN_NAME= the app's Managed Identity object ID - ✓A scoped role with
SELECTon only the approved product, nothing more - ✓Federated OAuth so the app pod gets a fresh token every connection, zero stored secrets
- ✓A clean revoke path:
DROP ROLEsevers access instantly, audit trail intact
The admin reviews each request
Data access requests, admin queue
via V2C portal| App | Data product | Status |
|---|---|---|
| Expense Categoriser | v_top_customers | APPROVED |
| Sales Forecast | monthly_revenue | PENDING |
| Refund Triage | product_performance | APPROVED |
| Marketing Cohorts | v_top_customers | DENIED |
✓ Per-app identity
Each app's pod authenticates as its own Managed Identity, no shared service accounts
✓ Auto-discovery
Tag a view in Snowflake; V2C onboards it within the hour, AGENTS.md follows
✓ Approval-gated deploys
Build pauses until the admin approves, no surprise data access in production
✓ Living catalog
Column comments flow from Snowflake's data dictionary into every app's AGENTS.md
A vibe-coded app, but the data side is enterprise-grade.
The developer never asked for credentials. The admin never copy-pasted a connection string. Snowflake's audit log shows exactly which app accessed which row, and you can revoke any one of them in a single click without touching the others.
Expense Categoriser
azure · uksouth · v2c-prod-aks · namespace v2c-expense-categoriser
Pods · ReplicaSet rs-7f8d9
3 / 3 ready · HPA 2–10 · CPU target 70%
expense-categoriser-7f8d9-x4k2p
age 3h · cpu 24m · mem 128Mi
expense-categoriser-7f8d9-qj9lm
age 3h · cpu 19m · mem 124Mi
expense-categoriser-7f8d9-wp2nr
age 12m · cpu 41m · mem 132Mi
10:14:02Z listening on :3000
10:14:03Z GET /health 200 · 3ms
10:14:11Z POST /api/classify 200 · 182ms
10:14:14Z APIM gpt-4o · tokens_in=412 · tokens_out=93
10:14:14Z POST /api/classify 200 · 614ms
Operate from the portal
Ship it. Watch it. Fix it.
Without leaving V2C.
Every project page has live pod status, readiness, and streaming logs built in. When something looks off, you don't switch to kubectl or the Azure portal, you open the project and act on it.
Pod-level readiness
Replica counts, per-pod phase + readiness probes, CPU / memory usage, polled live
Streaming container logs
Tail the last 200 lines of any pod with one click, right in the project view
Azure Resources tab
Every resource V2C provisioned for the project, with deep links into the Azure portal
Kill a stuck pod
Delete a pod from the UI, the ReplicaSet replaces it automatically
Identity · Microsoft Entra
Put your app behind Entra.
Don't touch the code.
Flip a switch on the project page and V2C provisions an Entra App Registration, generates the client secret, and rolls out an oauth2-proxy sidecar alongside the pod. Incoming traffic is authenticated against your tenant before it reaches the container. Your app stays the same.
01
Admin toggles auth on
One switch on the project's Access Control panel, no Azure portal, no manifests
02
V2C provisions + wires
App Registration, client secret, redirect URI, cookie secret, all created and stored in Key Vault
03
Sidecar does the auth
oauth2-proxy deploys alongside the app pod; every request is signed-in before it hits your container
Entra authentication
Authentication active
App registration
v2c-expense-categoriser
Tenant
your-org.onmicrosoft.com
Client secret
● stored in Key Vault
Sidecar
● oauth2-proxy rolled out
Access group, who can sign in
IaC · Spacelift-native
Same pane of glass
as the rest of your estate.
Delegate a subscription to V2C and it onboards itself into Spacelift automatically, creating a dedicated v2c space and a stack for every resource it manages on your behalf. Your engineers see every V2C deployment in the same Spacelift UI they already use for the rest of your estate.
The difference, your people don't commit Terraform. Business users deploy through V2C, and V2C keeps the Spacelift stacks in sync continuously. No PRs to review, no drift to chase.
V2C only touches its own space. Every other space, stack, and policy in your Spacelift org is off-limits, your existing IaC stays exactly as your platform team wrote it.
✓ Automatic onboarding
V2C space + stacks created when you connect Spacelift
✓ Continuous sync
Stack state follows what V2C deploys, no PR, no commit
✓ Scoped to V2C
Nothing outside the V2C space is ever touched
✓ Engineer-visible
Platform team sees v2c-managed infra alongside everything else
v2c-landing-zone
landing zone
v2c-prod-expense-categoriser
app · prod
v2c-prod-hr-policy-chatbot
app · prod
v2c-dev-contract-reviewer
app · dev
v2c-prod-support-triage
app · prod
10:14:03Z business user deployed contract-reviewer
10:14:05Z stack v2c-dev-contract-reviewer state updated
10:14:06Z spacelift reconciled · in sync
Route to live
Four phases.
Every one of them audited.
Register & connect
Register a project, point V2C at your GitHub repo, pick Azure. The landing zone is already there, it gets reused.
BuildKit build + deploy
V2C builds your container with BuildKit inside the cluster, pushes to your ACR, rolls out to the dev namespace. Every push. No local Docker.
Harden & gate
Turn on Entra auth, scope an access group, run availability tests, swap dev DNS for production DNS.
Release & approve
Promotions run through the Releases tab, pending → awaiting approval → active. Every phase logs to the audit trail.
Automated image refresh
Your apps are patched weekly.
You don't lift a finger.
Container base images are moving targets. node:20-alpine today is a different image than it was last week, every time upstream patches a CVE, the tag gets a new digest. V2C rebuilds your project weekly on a maintenance window you control, so those patches land in production automatically. The portal shows you exactly which digest is running and which digest is current upstream, even when everything matches, so you know the check actually happened.
Zero-downtime during refresh
The Deployment scales to 2 replicas only while the rolling update is in flight, the old pod serves traffic until the new one is Ready, then it hands over. Scale drops back to 1 immediately after.
Maintenance window, your timezone
Admins set an org default (e.g. Saturday 3am Europe/London). Project owners can override per-project. Only admins can pause, the whole point is everyone gets refreshed.
Upstream digest visibility
Every project shows its currently-running image digest, its base image reference, and the latest upstream digest for that tag, updated hourly. A banner tells you if you're behind.
Failed refresh? We retry tomorrow.
No stale apps. If a scheduled refresh fails, V2C keeps trying daily at the same window until it succeeds.
Maintenance tab · expense-categoriser
up to dateApp image running
expense-categoriser:a84f2e1
sha256:c7a9…f1e3
Base image
docker.io/library/node:20-alpine
on build sha256:8b1d…9a0c
upstream sha256:8b1d…9a0c ✓ matches
Next scheduled refresh
Saturday 3:00 AM Europe/London
org default · last refreshed 2 days ago
During refresh
→ scale to 2 replicas
→ rebuild from latest commit
→ rolling deploy (zero downtime)
→ scale back to 1 replica
Security posture
The things you'd ask
your platform team for.
Every V2C deployment lands inside a scoped resource group in your Azure subscription. The AKS clusters are private. Secrets live in Key Vault. Every app gets a dedicated workload identity, federated to its Kubernetes service account, no static credentials inside pods.
✓ AES-256-GCM credential storage
All stored creds encrypted with HKDF-derived keys
✓ Workload identity per app
Federated to the pod's service account
✓ Per-project APIM subscription
Revoke or throttle one app, don't touch the rest
✓ Encrypted at rest & transit
AES-256 + TLS enforced everywhere
✓ Audit log, append-only
Every phase transition, approval, reset
✓ Row-level authorisation
Non-admins only see projects they created
✓ Weekly image refresh
Base-image CVE patches land automatically during the maintenance window
What runs in your subscription
Side project
Hand V2C a subscription.
Let your people build.
Secure AI-powered apps, inside your own Azure tenant, under your own governance, with your coding agent already reading the rules.
A side project, not for sale