Secure vibe-coding · your cloud · IaC visible

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.

V2C · Onboarding · Azure landing zoneprovisioning
SubscriptionProduction (•••2a4f)Regionuksouthv2c-landing-zone-rg
01

Resource group, VNet, NSG

A scoped landing zone, not a flat estate

02

Two AKS clusters

v2c-dev and v2c-prod, private, workload identity enabled

03

Azure Container Registry

Images built in-cluster by BuildKit, pushed here

04

Key Vault

Per-app secrets, injected as env vars at pod startup

05

API Management (optional)

AI gateway, per-project subscriptions

running
06

DNS + wildcard TLS

Azure DNS zone + Let's Encrypt wildcard, fanned out

queued
07

Workload identities

Per-app, federated to the app's service account

queued
5 of 7 complete · ~2 minutes remaining
71%

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.

Claude Code · in-editor conversationMCP
toolsv2c (4)

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.

No portal opened. No form filled.streamable-http

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

AGENTS.md · generated by V2C · committed to repolive
your-org/expense-categorisermain
# 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 manually

AI 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

APIM_GATEWAY_URLhttps://<org>.azure-api.net
APIM_DEFAULT_PATHhttps://<org>.azure-api.net/openai/v1/chat/completions
APIM_DEFAULT_MODELgpt-4o
APIM_SUBSCRIPTION_KEY••••••••••••••
↳ Subscription key is per-app · provider API key lives on the APIM backend, never reaches your container

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
AppModelCallsTokens
Expense Categorisergpt-4o14328,420
HR Policy Chatbotgpt-4o8915,230
Contract Reviewergpt-4o3441,180
Support Triagegpt-4o5128,940
Total tokens today93,770

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"
  }
}
↳ V2C reads this on commit. Provisioning kicks off in the background; the project page shows a step-by-step progress terminal. Sizes: small · medium · large.
AGENTS.md · PostgreSQL section · auto-generatedlive
## 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 DefaultAzureCredential

V2C provisions, in your subscription

provision-postgres.sh · v2c-expense-categoriserready
subscriptionyour-org · uksouth
01

Dedicated workload resource group

v2c-{shortId}-{region}-rg, tagged for cleanup

02

Workload managed identity

Federated to the app's namespace ServiceAccount

03

PostgreSQL Flexible Server

AAD-only auth · TLS 1.2 · no admin password stored

04

Identity registered as AAD admin

Pod authenticates via DefaultAzureCredential

05

Database + firewall + connection details

PGHOST / PGUSER / PGDATABASE injected at deploy

5 of 5 complete · ready for connectionsAAD-only · TLS 1.2

injected env vars · at deploy time

PGHOSTv2c-3z9k7q-uksouth-pg.postgres.database.azure.com
PGDATABASEapp
PGUSERapp-3z9k7q (the workload identity)
PGSSLMODErequire
PG_AAD_SCOPEhttps://ossrdbms-aad.database.windows.net/.default
↳ No PGPASSWORD. The pod fetches a short-lived AAD token via its workload identity and uses it as the password, refreshed per connection.

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.

BL

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)
VEC

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
CR

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.

Step 1

Your user asks

“Build me an app that lets people ask about our internal policies and get the right answer back.”

Step 2

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.

Step 3

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.

Step 4

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"
    }
  ]
}
↳ Next deploy detects the new request. Build pauses with a clear status. The developer sees: “Awaiting admin approval”, no cryptic 403, no production accident.

What V2C generates on approval

  • A Snowflake user with LOGIN_NAME = the app's Managed Identity object ID
  • A scoped role with SELECT on 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 ROLE severs access instantly, audit trail intact

The admin reviews each request

Data access requests, admin queue

via V2C portal
AppData productStatus
Expense Categoriserv_top_customersAPPROVED
Sales Forecastmonthly_revenuePENDING
Refund Triageproduct_performanceAPPROVED
Marketing Cohortsv_top_customersDENIED
Pending review · platform admins only1 awaiting

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.

demo.v2c.app/projects/expense-categoriserproduction
EC

Expense Categoriser

azure · uksouth · v2c-prod-aks · namespace v2c-expense-categoriser

healthy
OverviewDeployReleasesAccess ControlAzure ResourcesDocs

Pods · ReplicaSet rs-7f8d9

3 / 3 ready · HPA 2–10 · CPU target 70%

polling · 5s

expense-categoriser-7f8d9-x4k2p

age 3h · cpu 24m · mem 128Mi

ready

expense-categoriser-7f8d9-qj9lm

age 3h · cpu 19m · mem 124Mi

ready

expense-categoriser-7f8d9-wp2nr

age 12m · cpu 41m · mem 132Mi

ready
Stream · x4k2p tailing

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

demo.v2c.app/projects/expense-categoriser · Access Control

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

SG-Expense-Categoriser-Users24 members
SG-Finance-Operations11 members
Request flow · through the sidecar
Browser · GET /dashboard
oauth2-proxy · verify Entra sessionsidecar
authenticated
your app · :3000 · unchanged200 OK
Your container never sees the sign-in flow. When a request reaches port 3000 it's already authenticated, with user identity headers injected by the proxy.

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

app.spacelift.io/your-org/spaces/v2cin sync
Spacev2c5 stacks

v2c-landing-zone

landing zone

healthy

v2c-prod-expense-categoriser

app · prod

healthy

v2c-prod-hr-policy-chatbot

app · prod

healthy

v2c-dev-contract-reviewer

app · dev

healthy

v2c-prod-support-triage

app · prod

healthy
V2C reconciler · live watching

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

Your engineers view · same Spacelift login · no new toolauto-managed

Route to live

Four phases.
Every one of them audited.

01
Local

Register & connect

Register a project, point V2C at your GitHub repo, pick Azure. The landing zone is already there, it gets reused.

02
Preview

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.

03
Security

Harden & gate

Turn on Entra auth, scope an access group, run availability tests, swap dev DNS for production DNS.

04
Production

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 date

App 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

Azure Subscription (yours)
Resource Group: v2c-landing-zone-rg
VNet 10.x.x.0/16 · private
AKS v2c-dev · private · workload identity
AKS v2c-prod · private · workload identity
NSG · default-deny ingress
Azure Container Registry (your images)
Key Vault (per-app secrets)
API Management (AI gateway, per-app subs)
Entra App Registration (per-app, optional)
Workload Identities (per-app)

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