`:
```ts
import { CoDuckError } from '@coduck/sdk';
try {
await coduck.meta();
} catch (err) {
if (err instanceof CoDuckError) console.error(err.code, err.message);
}
```
---
## Auth
Source: https://coduck.ai/docs/raw/sdk/auth.md
# Auth
Email/password authentication for your deployed app. Sessions are stored in an httpOnly cookie and managed by server helpers; the client gets a small `useUser()` hook.
> **Deploy required.** Auth needs `CODUCK_AUTH_KEY`, which CoDuck only injects when the project is **deployed**. Sign-in will not work in the in-editor preview — deploy first.
## Server helpers
`@coduck/sdk/server` wraps the cookie + session handling for Next.js (App Router). Use it in route handlers and server actions.
```ts
import {
getSession,
signInWithPassword,
signUpWithPassword,
signOut,
} from '@coduck/sdk/server';
```
| Function | Returns | Notes |
|---|---|---|
| `getSession()` | `{ user } \| null` | Reads + verifies the session cookie |
| `signUpWithPassword(email, password, name?)` | `AuthSession` | Creates the user **and** sets the cookie |
| `signInWithPassword(email, password)` | `AuthSession` | Logs in **and** sets the cookie |
| `signOut()` | `void` | Clears the cookie + invalidates the refresh token |
### Sign in (server action)
```ts
'use server';
import { signInWithPassword } from '@coduck/sdk/server';
import { redirect } from 'next/navigation';
export async function login(formData: FormData) {
await signInWithPassword(
String(formData.get('email')),
String(formData.get('password')),
);
redirect('/dashboard');
}
```
### Protect a server component
```tsx
import { getSession } from '@coduck/sdk/server';
import { redirect } from 'next/navigation';
export default async function Dashboard() {
const session = await getSession();
if (!session) redirect('/login');
return Welcome, {session.user.email}
;
}
```
### The `/api/me` route
The client hook reads the session from your own app via `GET /api/me`. Expose it with the server helper so the browser never touches the cookie directly:
```ts
// app/api/me/route.ts
import { getSession } from '@coduck/sdk/server';
export async function GET() {
const session = await getSession();
return Response.json(session ?? { user: null });
}
```
## Client hook
`@coduck/sdk/client` gives you the current user in a client component. It fetches `/api/me` (above).
```tsx
'use client';
import { useUser } from '@coduck/sdk/client';
export function UserBadge() {
const { user, loading, refresh } = useUser(); // useUser('/api/me') by default
if (loading) return null;
if (!user) return Sign in;
return {user.email};
}
```
`useUser()` returns `{ user: AuthUser | null, loading: boolean, refresh: () => Promise }`.
## Low-level client
For full control over tokens (custom flows, non-cookie clients), `@coduck/sdk/auth` exposes the raw client:
```ts
import { auth } from '@coduck/sdk/auth';
const session = await auth.login({ email, password });
// → { user, accessToken, refreshToken, expiresIn }
const user = await auth.me(session.accessToken);
const renewed = await auth.refresh(session.refreshToken);
await auth.logout(session.accessToken);
```
| Method | Returns |
|---|---|
| `signup({ email, password, name? })` | `AuthSession` |
| `login({ email, password })` | `AuthSession` |
| `refresh(refreshToken)` | `AuthSession` |
| `verify(accessToken)` | `AuthUser` |
| `me(accessToken)` | `AuthUser` |
| `logout(accessToken)` | `void` |
## Types
```ts
interface AuthUser {
id: string;
email: string;
name?: string | null;
createdAt: string;
}
interface AuthSession {
user: AuthUser;
accessToken: string;
refreshToken: string;
expiresIn: number; // seconds until the access token expires
}
```
---
## Forms
Source: https://coduck.ai/docs/raw/sdk/forms.md
# Forms
Capture submissions from any form (contact, waitlist, feedback) without writing a backend. Submitting is browser-safe; reading requires the API key, so it runs on the server.
```ts
import { forms } from '@coduck/sdk/forms';
```
## Submit (browser-safe)
`submit()` is public — call it from a client component. It only needs `NEXT_PUBLIC_CODUCK_PROJECT_ID` (injected at deploy).
```tsx
'use client';
import { forms } from '@coduck/sdk/forms';
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
const data = new FormData(e.currentTarget);
await forms.submit('contact', {
name: data.get('name'),
email: data.get('email'),
message: data.get('message'),
});
}
```
You can also import the bound helper directly:
```ts
import { submit } from '@coduck/sdk/forms';
await submit('waitlist', { email });
```
The `formName` is just a label you choose — submissions are grouped by it.
## Read submissions (server only)
`list()` and `markRead()` require `CODUCK_API_KEY`, so call them from a route handler or server component.
```ts
import { forms } from '@coduck/sdk/forms';
const recent = await forms.list({ formName: 'contact', limit: 50 });
await forms.markRead(recent[0].id);
```
| Method | Scope | Returns |
|---|---|---|
| `submit(formName, fields)` | browser-safe | `void` |
| `list({ formName?, limit? })` | server | `FormSubmission[]` |
| `markRead(id)` | server | `FormSubmission` |
Submissions also show up in your project's **Forms** tab in the cloud panel.
## Type
```ts
interface FormSubmission {
id: string;
projectId: string;
formName: string;
fields: Record;
submitterIp: string | null;
userAgent: string | null;
read: boolean;
createdAt: string;
}
```
---
## Storage
Source: https://coduck.ai/docs/raw/sdk/storage.md
# Storage
Per-project file storage for uploads, generated assets, exports — anything your app needs to keep. All methods require `CODUCK_API_KEY`, so use storage from the server.
```ts
import { storage } from '@coduck/sdk/storage';
```
## Upload
`file` can be a `Blob`, `Buffer`, or `Uint8Array`. Max **50 MB** per file. Names may contain letters, digits, and `+ . _ -`.
```ts
// e.g. inside a route handler
const form = await request.formData();
const file = form.get('file') as File;
const saved = await storage.upload(file.name, file);
// → { name, size, modifiedAt }
```
## List, link, delete
```ts
const files = await storage.list(); // StoredFile[]
const href = storage.url('logo.png'); // direct fetch URL (needs API key header)
await storage.delete('logo.png');
```
| Method | Returns |
|---|---|
| `list()` | `StoredFile[]` |
| `upload(name, file)` | `StoredFile` |
| `delete(name)` | `void` |
| `url(name)` | `string` |
> `url(name)` returns a direct link, but fetching it requires the API key in the `Authorization` header — so serve files through your own route rather than exposing the raw URL to the browser.
## Type
```ts
interface StoredFile {
name: string;
size: number; // bytes
modifiedAt: string;
}
```
---
## Analytics
Source: https://coduck.ai/docs/raw/sdk/analytics.md
# Analytics
Track custom events — signups, purchases, feature usage — alongside the pageview data CoDuck already collects for your project.
```ts
import { analytics } from '@coduck/sdk/analytics';
await analytics.track('signup_completed', { plan: 'pro' });
```
Or the bound helper:
```ts
import { track } from '@coduck/sdk/analytics';
await track('checkout_started', { cartValue: 49.0 });
```
| Method | Returns |
|---|---|
| `track(name, properties?)` | `void` |
`properties` is an optional `Record` of metadata for the event.
> Requires `CODUCK_API_KEY`, so call `track()` from the server (route handler or server action). **Pageviews are recorded automatically** — reach for `track()` only for the custom events that matter to your product.
---
## Install the CLI
Source: https://coduck.ai/docs/raw/cli/install.md
# Install the CLI
The CoDuck CLI ships on npm as **`@coduckai/cli`**. It's the same binary used by humans in their terminal and by AI agents calling into CoDuck from their host.
## Requirements
- **Node.js 20** or newer (we use modern fetch + native `Set` semantics)
- A POSIX shell (`bash` / `zsh` / `fish`) or PowerShell
- macOS, Linux, or Windows
## Global install (recommended)
```bash
npm install -g @coduckai/cli
```
Verify:
```bash
coduck --version
```
You should see a JSON line with the current version and the API base URL. If the `coduck` command isn't found, your `npm` global bin isn't on `$PATH` — see Troubleshooting below.
## After installing
Three commands to get going:
```bash
coduck login # browser opens, click Approve
coduck create-existing # in your project folder — creates a coduck.json
coduck chat # opens an interactive chat with the AI agent
```
That's it — you're now operating a CoDuck project from your terminal.
## One-off / no global install
```bash
npx @coduckai/cli@latest
```
Useful in CI or on someone else's machine. Slower per invocation because npm has to resolve the package each time.
## Updating
```bash
npm install -g @coduckai/cli@latest
```
The CLI **does not auto-update**. If you want to know what changed between versions, see the changelog on the [npm page](https://www.npmjs.com/package/@coduckai/cli).
## Uninstall
```bash
npm uninstall -g @coduckai/cli
rm -rf ~/.coduck
```
The second command removes your stored credentials and any `coduck.json`-cached state. (Your remote CoDuck account is untouched. To delete that, sign in to [coduck.ai](https://coduck.ai) and use account settings.)
## Output modes
The CLI behaves differently depending on whether its stdout is a terminal:
- **Interactive (TTY)** — pretty tables, colors, spinners, prompts
- **Piped (non-TTY)** — pure JSON, no colors, no prompts, exit codes carry the meaning
Force machine mode at any time with `--json`. Force "fail instead of prompting" with `--no-input`. Both can be combined for use inside agent loops.
```bash
coduck projects --json | jq '.[].id'
coduck deploy --no-input --json
```
## Troubleshooting
### `coduck: command not found`
Your npm global bin directory isn't on `$PATH`. Find it with:
```bash
npm config get prefix
```
The binary lives in `/bin`. Add that to your `$PATH` in your shell config (`~/.zshrc`, `~/.bashrc`, etc.).
### `Cannot find module ...node_modules\coduck-cli\...` on Windows
You have a stale shim from a previous attempted install of a different package name. Run these in Command Prompt:
```cmd
npm uninstall -g coduck-cli
npm uninstall -g @coduckai/cli
del "%APPDATA%\npm\coduck.cmd"
del "%APPDATA%\npm\coduck.ps1"
del "%APPDATA%\npm\coduck"
npm install -g @coduckai/cli@latest
```
The `del` commands may say "could not find" for some files — that's fine, keep going. Last command installs fresh.
### `EACCES: permission denied` during install
You're using a system Node where `npm install -g` needs sudo. Don't `sudo npm install` — instead, switch to a user-level Node manager:
- macOS / Linux: [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm)
- Windows: [nvm-windows](https://github.com/coreybutler/nvm-windows)
Then re-run `npm install -g @coduckai/cli` without sudo.
### `EBADENGINE` or "unsupported engine"
Your Node version is below 20. Upgrade Node.
## What's installed
The package contains:
- `bin/coduck.js` — the entry point (calls into compiled JS)
- `dist/` — compiled TypeScript output (~30 KB)
- Runtime deps: `commander`, `chalk`, `@inquirer/prompts`, `cli-table3`, `ora`, `form-data`
No native modules, no postinstall scripts, no telemetry.
## Next
You're ready to [sign in](/docs/cli/auth).
---
## Authentication
Source: https://coduck.ai/docs/raw/cli/auth.md
# Authentication
The CLI uses **browser-based device authorization**. You never type your CoDuck password into your terminal. Approval happens on a CoDuck web page that you're already signed in to.
## Signing in
```bash
coduck login
```
What happens, step by step:
1. CLI prints a short confirmation code like `AB7K-9XQM` and opens your browser to `https://app.coduck.ai/cli?code=AB7K-9XQM`.
2. If you're not signed in to the web app, you're sent to `/login` first, then bounced back.
3. The page shows the device the CLI is asking for ("your hostname / your OS") and an Approve / Deny button.
4. You click **Approve**.
5. Within ~5 seconds, the CLI receives the token and writes it to `~/.coduck/credentials.json` (file mode `0600`).
That's it. From this point on, every CoDuck command works without prompting.
> [!NOTE]
> The CLI has no `--password` flag and never reads your password from stdin or an env var. If you're seeing a doc anywhere that mentions one, it's outdated.
## Token lifetime
| What | How long |
|---|---|
| Initial token | 90 days |
| Sliding extension on each use | +30 days, capped at 180 from issue |
| Hard maximum lifetime | 180 days from creation |
| Idle expiry | 90 days of no use |
In practice: if you use the CLI even occasionally, you'll never get logged out. If you stop using it for 90 days, your next call will 401 and you'll need to `coduck login` again.
## Managing tokens
Every device gets its own token. You can have many — one per laptop, one for CI, etc.
### List your tokens
```bash
coduck token list
```
Shows each token with its device name, last-used timestamp, last IP, and expiration.
### Revoke a single token
```bash
coduck token revoke
```
Takes effect within ~30 seconds (the API caches the revocation list for performance).
### Revoke every token on your account
```bash
coduck logout --all
```
Use this if you think a token might have been exposed. Your web session is untouched.
### Sign out from just this machine
```bash
coduck logout
```
Revokes only the current device's token and clears `~/.coduck/credentials.json`. Other devices keep working.
## Where your credentials live
```
~/.coduck/
└── credentials.json (mode 0600)
```
The file contains your token, the API URL, your user ID, and your email. **Don't share it.** Anyone with this file has full account access until the token expires or you revoke it.
If you accidentally print or share it, run `coduck logout --all` immediately and `coduck login` to issue a fresh token.
## Inspecting the current token
```bash
coduck token status # plan + credits, no token printed
coduck whoami # who you're signed in as
coduck token show --confirm # actually print the token (refuses without --confirm)
```
The `--confirm` requirement on `show` is intentional: by default it refuses to leak the token to stdout, where it could end up in shell history or someone else's screen.
## For CI / GitHub Actions
`npm login` doesn't work in CI. The token-based flow is what you want anyway:
1. Run `coduck login` once on your local machine.
2. `cat ~/.coduck/credentials.json` — copy the `token` field.
3. Add it to your CI secrets as `CODUCK_TOKEN`.
4. In CI:
```yaml
- run: npm install -g @coduckai/cli
- run: |
mkdir -p ~/.coduck
cat > ~/.coduck/credentials.json <
## Commands reference
Source: https://coduck.ai/docs/raw/cli/commands.md
# Commands reference
Every CoDuck CLI command, grouped by what it operates on. All commands honor `--json` (machine-readable output), `--no-input` (fail instead of prompting), and real exit codes (0 ok, 2 usage, 3 auth, 4 not found, 5 conflict, 6 network, 7 server — full table at the bottom).
For installation, see [Install the CLI](/docs/cli/install). For sign-in, see [Authentication](/docs/cli/auth).
## Chat with the agent
Send a prompt to a project's AI agent, stream the response. Designed for AI agents calling CoDuck programmatically — JSON-first, NDJSON streaming, no interactive prompts.
```bash
coduck chat "add a login page" # streams response, exits
coduck chat "add a login page" --json # NDJSON output for agents
coduck chat "add a login page" --project # override coduck.json
coduck chat --history # print recent messages
coduck chat --history --json # same, as JSON
```
`coduck ask "..."` is an alias for `coduck chat "..."`.
### NDJSON event types (what an agent sees with `--json`)
| event | what it means |
|---|---|
| `chunk` (type=`ttft`) | First token arrived; includes time-to-first-token in ms |
| `chunk` (type=`text` or `response`) | Streaming response text |
| `chunk` (type=`tool_call`) | Agent called a tool: `tool`, `path`, args |
| `chunk` (type=`code`) | Wrote/edited a file: `path`, `content` |
| `chunk` (type=`preview_url`) | Sandbox boot, ephemeral preview URL |
| `chunk` (type=`usage`) | Tokens used in this round |
| `chunk` (type=`complete`) | Generation finished server-side; cost, file list |
| `complete` | Final summary (mirror of last chunk) |
| `done` | CLI-side wrap-up event with totals |
| `paywall` | Insufficient credits; includes plan + upgrade hint |
| `error` | Failure; includes `message` |
## Projects
```bash
coduck projects # list your projects
coduck project # show one project's details
coduck create --name # create a new AI-scaffolded project
coduck create-existing # import the current directory as a project
coduck rename # rename a project
coduck delete --yes # delete a project (irreversible)
coduck pause # pause (stops billing, keeps state)
coduck resume # resume a paused project
```
## Files
```bash
coduck push # upload local files (honors .gitignore + .coduckignore)
coduck push --dry-run # show file count, total size, biggest dirs — no upload
coduck push --force # push even if the server has newer files
coduck pull # download project files locally
coduck files ls # list remote files
coduck files cat # print a remote file
```
`push` uploads the source dir from your [`coduck.json`](/docs/reference/coduck-json)
and delivers the config file itself (even from a subdirectory). Transient network
errors retry automatically; `deploy` warns if your last push failed or local files
changed since.
## Deploy
```bash
coduck deploy # build + ship the project
coduck deploy --size large # deploy on a bigger instance (small|medium|large)
coduck stop # stop the running container
coduck restart # restart with no config change
coduck status # current deployment status
coduck teardown --yes # destroy container + DB + vhost
coduck logs --follow # tail container logs
coduck logs --since 30m --grep error # filter by age + pattern
coduck logs-digest # summarized error/warn digest
```
`deploy` uses the commands + `instanceSize` from your
[`coduck.json`](/docs/reference/coduck-json); `--size` overrides the tier for one
deploy. A failed deploy reports the specific cause (out of memory, build failed,
wrong port, crashed after start).
## Generation (AI agent)
```bash
coduck generate "add a login page" --wait # send a prompt to the agent
coduck jobs status # check a running generation
coduck jobs messages # conversation history
```
## Environment variables
```bash
coduck env list # list env vars (values masked)
coduck env list --reveal # show full values
coduck env get
coduck env set
coduck env unset
coduck env import .env # bulk import (skips reserved/invalid keys, imports the rest)
coduck env reserved # list the keys CoDuck manages (can't be set)
```
A set of keys are reserved (CoDuck injects them and rejects them on write):
`DATABASE_URL`, `DIRECT_URL`, `PORT`, `NODE_ENV`, and everything under `CODUCK_*` /
`NEXT_PUBLIC_CODUCK_*`. `coduck env reserved` prints the authoritative list; see the
[`coduck.json` reference](/docs/reference/coduck-json#reserved-environment-variables).
`coduck env import` skips reserved/invalid keys with a warning instead of aborting.
## Custom domains
```bash
coduck domains list
coduck domains add example.com
coduck domains verify example.com
coduck domains remove example.com
coduck domains transfer example.com
```
## Database
```bash
coduck db schema # full schema as JSON
coduck db tables # list tables
coduck db tables --table users # read rows from a table
coduck db users # auth users (if using @coduckai/sdk/auth)
```
Read-only by design. No raw connection URL is exposed.
## Backups
```bash
coduck backups list
coduck backups create
coduck backups download --out backup.sql.gz
coduck backups restore --yes
coduck backups delete
```
## Email
```bash
coduck email status
coduck email domains list
coduck email domains add example.com
coduck email domains dns # reprint DNS records
coduck email domains verify
coduck email send --from noreply@example.com --to user@example.com \
--subject "Welcome" --text "Thanks." --html "Thanks.
"
coduck email usage # quota
coduck email messages # recent messages
coduck email suppressions list
coduck email suppressions remove
coduck email unpause # resume after auto-pause
```
## Storage
```bash
coduck storage list
coduck storage upload
coduck storage get --out local.bin
coduck storage remove
```
## Stripe (BYOS payments)
```bash
coduck stripe connect # print OAuth URL
coduck stripe status
coduck stripe disconnect --yes
coduck stripe payments stats
coduck stripe payments transactions
coduck stripe payments revenue --days 30
coduck stripe payments health
```
## Activity, analytics, forms
```bash
coduck activity --limit 100
coduck analytics --days 30
coduck forms list
coduck forms read
coduck forms delete
```
## Account + tokens
```bash
coduck whoami
coduck token status # plan, credits
coduck token list # CLI tokens on your account
coduck token revoke
coduck token show --confirm # print the JWT (refuses without --confirm)
```
## Discovery
```bash
coduck --help # human-friendly help
coduck --help --json # machine-readable spec (use this to build an MCP wrapper)
coduck version
coduck doctor # diagnose auth + config + connectivity
```
## Global flags
| Flag | What it does |
|---|---|
| `--json` | Force JSON output regardless of TTY |
| `--no-input` | Fail with exit 2 instead of prompting |
| `--quiet` | Suppress non-essential output |
| `--project ` | Override the `coduck.json` projectId for this command |
## Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error |
| 2 | Usage error (bad flag, missing input under `--no-input`) |
| 3 | Authentication error |
| 4 | Not found |
| 5 | Conflict (e.g. payment required / state conflict) |
| 6 | Network error |
| 7 | Server error (5xx) |
## Next
- [Install the CLI](/docs/cli/install)
- [Authentication](/docs/cli/auth)
---
## coduck.json reference
Source: https://coduck.ai/docs/raw/reference/coduck-json.md
# coduck.json reference
`coduck.json` is the source of truth for how CoDuck builds and runs your project.
`coduck create-existing` writes it; you can edit it by hand. It is **re-read on
every deploy** — change a command, `coduck push`, `coduck deploy`, and the new
command runs. (Earlier behavior baked config at create-time; that is no longer
the case.)
```jsonc
{
"projectId": "ff0c35e6-…", // set by create-existing; don't change
"name": "my-app",
"dir": "./", // source root to push + run (see "Monorepos")
"runtime": "node20", // node20 | node22 | static
"install": "npm ci", // optional — default: npm install --include=dev
"build": "npm run build", // optional — default: npm run build
"preStart": "npm run migrate", // optional — runs after build, before start
"start": "node server.js", // optional — default: npm start
"port": 3000, // informational; bind process.env.PORT (see below)
"instanceSize": "small" // small | medium | large
}
```
## How each field drives the deploy
A deploy runs these steps in order, in your container:
1. **install** — your `install`, else `npm install --include=dev --no-audit --no-fund`.
(Dev deps are installed by default because build tools like Tailwind/PostCSS
live there. If you override with `npm ci`, note `NODE_ENV=production` is set, so
add `--include=dev` yourself if your build needs dev deps.)
2. **prisma generate** — only if `prisma/schema.prisma` exists *and* declares
`model`s. Skipped otherwise.
3. **build** — your `build`, else `npm run build`.
4. **schema bootstrap** — if `prisma/schema.prisma` declares models *and* you have
**no** `preStart`, CoDuck runs `prisma db push`. If you set `preStart`, CoDuck
does **not** auto-push — you own schema setup (see [Database](/docs/database/overview)).
5. **preStart** — your `preStart` (e.g. `prisma migrate deploy`, `drizzle-kit push`,
`node scripts/migrate.js`). The right place for migrations.
6. **start** — your `start`, else `npm start`.
**Skip a step** by setting it to `""` (empty string). E.g. a static/no-build app:
`"build": ""`.
## Ports — bind `process.env.PORT`
CoDuck assigns the port and injects it as `PORT`. **Your app must listen on
`process.env.PORT`**, not a hardcoded value. The `port` field in `coduck.json` is
informational only — if your app hardcodes `3000` but CoDuck health-checks the
assigned port, the deploy fails with "responding on :3000 but health-checking
: — bind process.env.PORT".
## Instance size
`instanceSize` (or `coduck deploy --size`) selects container resources. The CLI
flag wins; otherwise `coduck.json` is used; default `small`.
| Size | Memory | CPU | Plan required |
|---|---|---|---|
| `small` | 1.5 GB | 1.0 | any paid plan |
| `medium` | 2.5 GB | 2.0 | Pro or Studio |
| `large` | 4 GB | 3.0 | Studio |
A request above your plan is clamped down (and the deploy tells you). The Node
build-heap ceiling auto-scales to ~80% of the tier, so heavy client builds
(three.js, remotion, recharts) that OOM on `small` usually succeed on `large`.
## Monorepos / subdirectory source
`dir` can point at a subdirectory (e.g. `"./web"`) while `coduck.json` stays at the
repo root. The CLI uploads the repo-root `coduck.json` (and root
`.gitignore`/`.coduckignore`) alongside the subtree so the server still sees your
config. **This requires `@coduckai/cli` ≥ 0.1.11** — older CLIs pushing a subdir
silently omitted `coduck.json`, so declared commands were ignored. Run
`coduck --version` and upgrade if needed.
One CoDuck project = one container. For a separate frontend + backend, wrap them
in a single `start` (e.g. `concurrently`) or create two projects.
## Reserved environment variables
CoDuck injects these into every container and **rejects them on write** (setting
them has no effect — platform values win). The live, authoritative list is
`coduck env reserved`.
| Key / pattern | What it is |
|---|---|
| `DATABASE_URL` | Pooled Postgres connection string (see [Database](/docs/database/overview)). |
| `DIRECT_URL` | Direct (non-pooled) Postgres URL for migrations/`prisma db push`. |
| `PORT` | The port to listen on. Bind `process.env.PORT`. |
| `NODE_ENV` | Set to `production` in the container. |
| `HOME` | Container home dir. |
| `CODUCK_*` | All CoDuck-managed keys (`CODUCK_API_KEY`, `CODUCK_AUTH_KEY`, `CODUCK_PROJECT_ID`, `CODUCK_API_URL`, `CODUCK_STORAGE_DIR`, …). |
| `NEXT_PUBLIC_CODUCK_*` | Browser-exposed CoDuck values (project id, auth/api URLs, subdomain). |
Stripe keys (`STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`,
`NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`) are injected **only when you connect
Stripe** — until then you may set them yourself.
`coduck env import .env` skips reserved/invalid keys with a warning and imports
the rest, so a `.env` containing `DATABASE_URL` won't abort the whole import.
## Next
- [Deploying](/docs/projects/deploy) — the deploy lifecycle + failure messages.
- [Import an existing project](/docs/projects/create-existing)
- [Database](/docs/database/overview) — the managed Postgres starts empty.
---
## Runtime architecture
Source: https://coduck.ai/docs/raw/reference/runtime.md
# Runtime architecture
This page describes the runtime environment a deployed project actually runs
in: the container, the network, the database connection, the filesystem. Use
it when you're reasoning about what `process.env.DATABASE_URL` connects to,
what `localhost` means inside your container, or what sits between your app
and Postgres.
## Container model
Each deployed project runs in its **own Docker container on CoDuck's VPS**.
One project, one container. The base image is Node (Node 20 by default; pick
another via `runtime` in [`coduck.json`](/docs/reference/coduck-json)). Your
code is bind-mounted at `/app` as the working directory, and the container
runs as a non-root user (uid `1001`, the image's `coduck` user).
Container resource caps (CPU, memory) come from the instance size — see
[Deploying → Instance size](/docs/projects/deploy#instance-size-resources)
for the table.
## Networking
Containers run with Docker's `--network=host`. The consequences are worth
spelling out, because they're different from the bridged-network mental
model most "sandboxed hosting" platforms use:
- **`localhost` inside your container is the VPS's loopback.** A socket to
`127.0.0.1:6432` from your app code is the same kernel socket as a socket to
`127.0.0.1:6432` from the host process. There is no NAT, no
`docker-proxy`, no userspace TCP shim.
- **No `-p host:container` mapping.** Your app binds the `PORT` it was
assigned directly on the host. nginx upstreams to that port for
`.coduck.app` traffic (and any [custom domain](/docs/domains/custom-domains)).
- **External egress** (calls to public services — your own external API,
Stripe, OpenAI, etc.) goes out the host's interface normally. Nothing
intercepts it.
When something in this environment looks like "a local proxy" — it isn't.
The only thing between your app and `127.0.0.1:` is the kernel's
loopback interface.
## Database connectivity
Two database env vars are injected on every deploy. Both are reserved
(you can't override them via `coduck env set` — see
[Environment variables](/docs/projects/deploy#environment-variables)):
| Env var | Points at | What it's for |
|---|---|---|
| `DATABASE_URL` | `postgres://…@127.0.0.1:6432/?pgbouncer=true&connection_limit=1` | **Runtime queries — all stacks.** The host:port is **PgBouncer**, running on the VPS in transaction-pool mode with server-side prepared statements enabled (`max_prepared_statements = 100`). Prisma reads `?pgbouncer=true` to disable its client-side prepared-statement caching; other clients (`node-postgres`, `pg`, `drizzle-orm`, `knex`, `sequelize`) just send prepared statements normally — PgBouncer caches them per backend. `connection_limit=1` keeps Prisma clients to one server connection per request. |
| `DIRECT_URL` | `postgres://…@127.0.0.1:5433/` | **Schema operations only.** `prisma db push` and `prisma migrate deploy` need the schema engine to talk directly to Postgres (their connection pattern doesn't match what the pool expects), so CoDuck routes those to a direct pooler-bypass URL. `127.0.0.1:5433` is the project's Postgres cluster bound directly to loopback. You should not use this URL for runtime queries — use `DATABASE_URL`. |
> [!WARNING]
> **Don't `DROP SCHEMA public CASCADE` on your project DB.** PgBouncer authenticates
> incoming connections by calling a `public.user_lookup()` function in your DB; if
> you drop and recreate `public`, that function (and the schema-level `USAGE` grant
> to `pgbouncer_auth`) goes with it. `DATABASE_URL` will start returning
> `bouncer config error` on every query until support restores them. Use
> `prisma migrate` / `drizzle-kit drop` etc. instead, which target your own tables
> without nuking `public`.
Both `6432` and `5433` are loopback-only — neither is reachable from outside
the VPS. That's why there's [no raw external connection URL](/docs/database/overview#why-theres-no-raw-connection-url).
PgBouncer's transaction-pool mode means a single client connection to `:6432`
is served by any of N backend Postgres connections, transparently and
per-transaction. That's why your **client-side pool should stay small**
(≤ 10) — opening many client connections doesn't help when PgBouncer is
already pooling on the server side, and oversized client pools waste
PgBouncer slots. See [Connection pooling](/docs/database/overview#connection-pooling).
## Filesystem
- **Working directory:** `/app`, bind-mounted from CoDuck's project store on
the host. Reads and writes both work — your build can write `node_modules`,
`.next/`, etc. and they persist across redeploys of the same project.
- **User:** `uid 1001` (`coduck`, non-root). `HOME` is set to `/tmp` so npm's
cache/lockfile machinery has somewhere to write without trying to touch
`/root`.
- **Restart policy:** `on-failure` with 2 retries. If your app crashes 3
times in a row, the container stays stopped and the deploy reports it —
redeploy to restart.
## Common questions this page answers
- **What does `process.env.DATABASE_URL` connect to?** → PgBouncer on
`127.0.0.1:6432` (transaction-pool mode), on CoDuck's VPS loopback. Works for
all stacks — Prisma, drizzle-orm, node-postgres, knex, sequelize.
- **Why is `localhost:5433` in my env (`DIRECT_URL`)?** → That's the
pooler-bypass connection used by Prisma's schema engine at deploy time. It's
not the runtime path.
- **Is `localhost` inside my container the same as `localhost` on the
host?** → Yes — `--network=host`.
- **Is there a proxy between my app and Postgres?** → PgBouncer (a connection
pooler, not a proxy in the security sense) sits on the host on `:6432`. The
network hop between your container and PgBouncer is a direct loopback
socket — no shim or proxy in addition to PgBouncer itself.
## Next
- [Deploying](/docs/projects/deploy) — instance sizes, deploy lifecycle, failure modes.
- [Database overview](/docs/database/overview) — schema, migrations, backups.
- [`coduck.json` reference](/docs/reference/coduck-json) — the per-project deploy config.
---
## Changelog
Source: https://coduck.ai/docs/raw/changelog.md
# Changelog
What's new in CoDuck, newest first. **Platform** changes (hosting, deploy, the
API, the web app) are live on `coduck.ai` / `api.coduck.ai` as soon as they
land. **CLI** changes ship in an `@coduckai/cli` release — run
`npm i -g @coduckai/cli@latest` to get them, and `coduck --version` to check
what you have. **SDK** changes ship in an `@coduck/auth` release — run
`npm i @coduck/auth@latest` in your project to upgrade.
> **Agents:** if a deploy behaves differently than you expect, scan the Platform
> section first — behavior may have changed. The machine-readable copy of this
> page is at [`/docs/raw/changelog.md`](https://coduck.ai/docs/raw/changelog.md),
> and it's indexed in [`/llms.txt`](https://coduck.ai/llms.txt).
## Platform — 2026-05-26
- **Settings → Billing shows your plan + credit burn rate.** The top of
the billing panel now displays "Your plan: Pro / Plus / Studio" (or
"Free — not subscribed"), and the credits line shows "$5.00 / $20.00
this month" so you can see how much of your monthly allowance is
left. Top-up balance is shown separately because it doesn't have a
monthly cap and never expires.
- **The pricing page now knows what plan you're on.** When you're signed
in and subscribed, your plan card shows a "Current" badge and its
button is disabled with "Current plan" text. The other paid cards
relabel as "Upgrade to X" or "Downgrade to X" so the action you're
about to take is obvious before you click.
- **Pricing page layout cleanup.** Pro / Plus / Studio now sit in their
own three-column row as the self-serve options, with Enterprise
broken out as a slim "Contact sales" row beneath. Each of the three
main plan cards has more room to breathe, and Enterprise no longer
looks like a fourth equal-weight option.
- **Switching plans now actually works for existing subscribers.**
Previously, clicking any plan card on the pricing page or in Settings →
Billing while you were already subscribed silently failed (the request
came back as "Already subscribed"). The buttons now route you to the
Stripe customer portal — the right place to switch up or down, change
your card, or cancel — and the click goes through immediately.
- **Settings → Billing buttons now say "Upgrade ↑" or "Downgrade ↓"**
depending on whether the plan you're clicking is above or below your
current one. Previously they all said "Upgrade" regardless, so a
Studio user looking at the Plus card was told to "upgrade" to a
cheaper plan.
- **Settings → Billing no longer shows a "Free" plan card.** CoDuck's
hosting is paid-only — a free account can't deploy a project at all
— so listing Free as a "plan" next to Pro/Plus/Studio was misleading.
The billing page now shows just the three real plans you can
subscribe to. If you're not subscribed, no plan card is highlighted
as your current one (the Credits remaining panel above still shows
your true balance).
- **New Plus plan ($100/mo or $1,000/yr).** A middle tier between Pro and
Studio for builders shipping more than they thought. Plus gets $100 of
AI credits per month, Lake hosting (1 GB database), and a 15,000-emails-
per-month send limit — slotting cleanly between Pro (Pond / 100 MB / 5k
emails) and Studio (Ocean / 5 GB / 50k emails). Annual billing saves
$200/year (2 months free).
- **Pricing cards now list what you actually get.** Every plan card on the
pricing page and in Settings → Billing now shows credits, hosting tier
name, database size, monthly email-send cap, and key features — instead
of a one-line tagline. The numbers are the same values the platform
enforces, so what you see on the card is what you get.
- **Settings → Billing "Top up credits" section refreshed.** Clearer
heading and description so it's obvious that top-up credits stack on
top of your monthly subscription and are spent first. Each amount
button now shows the credit value ("$20 credits") rather than the
generic "one-time" caption. Plus subscribers can top up too, not just
Pro and Studio.
- **Billing page in Settings now lets you upgrade to Studio.** The Studio
plan ($200/mo — same product as Pro, 10× the runway) is now visible
alongside Free and Pro under Settings → Billing. Clicking **Upgrade →** on
either Pro or Studio goes straight to a Stripe Checkout for that plan.
- **Add Credits buttons in Settings → Billing fixed.** The top-up grid now
shows **$5 / $20 / $50 / $100** and each one works — previously the
buttons offered amounts CoDuck didn't accept, so clicks failed silently.
Studio subscribers can now top up too (it used to be Pro-only).
- **Out-of-credits modal: top-up buttons fixed.** When you run out of
credits mid-build, the four quick-top-up buttons inside the paywall
modal ($5 / $20 / $50 / $100) now correctly open Stripe Checkout — they
were previously hitting a route that didn't exist and 404'ing. The
"Subscribe to CoDuck Pro" button next to them was unaffected.
- **`DATABASE_URL` now works for every Postgres client, not just Prisma.**
Before today, apps using `node-postgres`, `pg`, `drizzle-orm`, `knex`, or
`sequelize` would hit `bouncer config error` when trying to query through
`DATABASE_URL` (the pooled path at `127.0.0.1:6432`) and had to fall back to
`DIRECT_URL` — which has no server-side pool, so bursty traffic could
exhaust it. PgBouncer is now configured with server-side prepared statements
enabled, so prepared-statement-using clients work on the pooled path
alongside Prisma. **Use `DATABASE_URL` for runtime queries regardless of
stack;** `DIRECT_URL` is only for `prisma db push` / `prisma migrate deploy`.
- **New runtime docs page:** [Runtime architecture](/docs/reference/runtime)
documents the container model, `--network=host` networking, exactly what
host:port `DATABASE_URL` and `DIRECT_URL` point at, and a `[!WARNING]`
about a footgun — **don't run `DROP SCHEMA public CASCADE` on your project
DB.** That command destroys the `public.user_lookup()` function PgBouncer
needs for connection auth (and the `USAGE` grant on the schema). If you've
already done it and you're seeing `bouncer config error`, contact support to
restore it.
## Platform — 2026-05-25
- **`coduck.json` is honored on every deploy.** Your `install`, `build`,
`preStart`, and `start` commands are re-read from `coduck.json` on each deploy
and run as declared — previously the container always ran a hardcoded
`npm install` → `npm run build` → `npm start`. Notes:
- `preStart` runs after build, before start — the right place for DB migrations
(e.g. `prisma migrate deploy`). Declaring it also disables CoDuck's automatic
`prisma db push`, so you own schema setup.
- Set any command to `""` to skip that step (e.g. `build: ""` for a no-build app).
- Monorepo note: this only takes effect once your CLI delivers `coduck.json` to
the server — see the CLI 0.1.11 entry. Older CLIs pushing a subdirectory did
not upload the repo-root `coduck.json`.
- **Specific deploy-failure messages.** A failed deploy now tells you the cause
instead of one generic "crashed before becoming healthy":
- out of memory → "retry on a bigger instance: `coduck deploy --size large`"
- build failed → fix the error and redeploy
- wrong port → "bind the port CoDuck assigns — use `process.env.PORT`"
- crashed after start → check `coduck logs`
- **Faster reinstalls for imported apps.** Projects with no `@coduck/*`
dependency now reuse the cached `node_modules` across deploys instead of a full
reinstall every time (the cache-bust only runs for projects that use the CoDuck
SDK).
- **Instance sizes.** `coduck deploy --size small|medium|large` (or
`instanceSize` in `coduck.json`) selects more build/runtime RAM + CPU;
medium/large require a Pro/Studio plan. The Node build-heap ceiling now uses the
container's full RAM, fixing out-of-memory builds for heavy client bundles
(three.js, remotion, recharts, …).
- **Reserved env keys.** The set of keys CoDuck manages (`DATABASE_URL`, `PORT`,
`NODE_ENV`, `CODUCK_*`, …) is complete and rejected clearly on write. List them
with `coduck env reserved`. The managed Postgres starts empty — reach it at
`DATABASE_URL`/`DIRECT_URL` and bootstrap your schema in `build` or `preStart`.
## CLI — Unreleased (0.1.11)
Merged; ships on the next `@coduckai/cli` publish.
- **Monorepo `coduck.json` delivery.** A subdirectory push (`dir: "./web"`) now
uploads the repo-root `coduck.json` to the server, so the platform's
deploy-config honoring (above) actually applies to monorepo layouts. Repo-root
`.gitignore`/`.coduckignore` are honored for subdir pushes too.
- **`coduck env import` no longer aborts on the first reserved key** — it skips
reserved/invalid keys with a warning and imports the rest. New `coduck env
reserved` lists managed keys.
- **`push` honors `.gitignore`** (plus sensible defaults: `node_modules`,
`.venv`, build output, caches). `push --dry-run` prints a size summary +
biggest dirs instead of a full file manifest.
- **Resilient push.** `push` auto-retries transient network errors; `deploy`
warns when your last push failed or local files changed since it.
- **Logs filters.** `coduck logs --since <30m|1h|…>` and `--grep `.
- **`coduck --version`** reads the installed package version correctly (no longer
under-reports).
## CLI 0.1.10
- `coduck deploy --size` and `coduck.json` `instanceSize` for instance-tier
selection.
## CLI 0.1.9
- Chunked `push` for large projects (no more 413s) and `.coduckignore` support.
## CLI 0.1.8
- `coduck db import ` — upload a SQL dump to the project database.
## CLI 0.1.7
- Real `coduck push` and `create-existing --push --deploy`; fixed `coduck logs`.
- Browser-based device login (`coduck login`) — the CLI never sees your password.
---