gipity deploy pushes your project live. It runs a multi-phase pipeline server-side and is the step most likely to surface mistakes - so deploy early and often, and read the phase results every time.
The build loop
gipity add → edit files → gipity deploy dev --inspect → fix errors → repeat
--inspect runs the page-inspect report (console, failed resources, timing, overflow) on the live URL right after the deploy - one command instead of deploy-then-inspect. Deploy to dev while building; deploy to prod only when it works. See app-debugging for the inspect step and its standalone form.
Commands
gipity deploy dev # → https://dev.gipity.ai/<account>/<project>/
gipity deploy prod # → https://app.gipity.ai/<account>/<project>/
| Flag | Effect |
|---|---|
--inspect [path] |
After a successful deploy, run page inspect on the deployed URL (optionally URL + path) in the same command |
--only <phases> |
Run only these phases, comma-separated (e.g. --only database) |
--force |
Re-run every phase, ignore checksums |
--optimize |
Force Vite build optimization on (it's already the default for prod; use this to optimize a dev deploy too). Only bundles <script type="module"> references |
--no-optimize |
Skip build optimization, upload files as-is. The escape hatch for plain-HTML apps: a prod deploy optimizes by default, which aborts on a plain <script src> tag - pass this to deploy them as-is |
--source-dir <dir> |
Deploy from a directory other than the project root |
--no-sync |
Skip the sync-up that normally runs before deploy |
--json |
Machine-readable output (the phases array) |
gipity.yaml - the deploy manifest
A project without gipity.yaml gets a plain static-file deploy (the src/ folder → CDN). A project with gipity.yaml gets the full multi-phase pipeline. The web-fullstack and api templates ship one; web-simple does not.
Deploy is opt-in: it ships only the folders a phase names as its source (src/ to the CDN, plus migrations//functions/ as backend). Anything else at the project root is kept but never deployed - that's by design. Put reference material you want to keep (diagrams, decks, notes) in docs/ and throwaway scratch (conversions, intermediate output) in tmp/ (sync-ignored, like *_tmp/ and .gipityscratch/); neither bloats a deploy. The trap is a bulky output dir dropped loose at the root (out/, vsd_out/) - those aren't scratch, so they sync and re-hash on every deploy; move them to docs/ (keep) or tmp/ (toss). See the "Where files go" section of the Files-and-sync guidance.
version: 1
deploy:
phases:
# Static frontend → CDN
- name: files
type: static
source: src
# SQL migrations → user database (idempotent, each runs once)
- name: database
type: sql
source: migrations
database: {{DATABASE}}
# Serverless functions → HTTP endpoints
- name: functions
type: functions
source: functions
function_definitions:
- name: list-items
auth: public
tables: [items] # grant DB access; add fetch_domains: [...] to call out
# Automated workflows → reconciled into real workflows + scheduled
- name: workflows
type: workflows
source: workflows
Phases
Phase type |
Source | What it does |
|---|---|---|
static |
src/ |
Uploads frontend files to the CDN |
sql |
migrations/ |
Auto-creates the database and runs each migration once (idempotent). It also re-runs seed files only if the phase declares an explicit seed: <dir> key - there is no automatic seeds/ folder; migrations/ is the only default source. To keep reference rows alive through gipity test's DB reset, see test.preserve in app-testing. |
functions |
functions/ |
Deploys each .js file as an HTTP endpoint with its declared permissions |
workflows |
workflows/ |
Parses each .yaml and creates/updates the workflow (idempotent), then arms its schedule |
records |
(declared inline) | Exposes existing DB tables via the native Records API - sets each table's auth_level (public = anonymous writes), searchable, primary key. Idempotent. Place AFTER the sql phase. |
services |
(declared inline) | Sets the billing mode of your app's AI services (llm, image, transcribe, …) - who pays when the service is called. Idempotent. |
A services phase declares who pays for each AI service - the in-source counterpart of the project_settings tool, so an app's billing choice ships with its gipity.yaml and is reproduced on every deploy (to a fresh project, a fork, dev or prod) instead of living only as out-of-band server state:
- name: services
type: services
service_definitions:
- service: llm
billing_mode: owner_pays # you (the app owner) pay; no user login needed
- service: image
billing_mode: owner_pays
- service: transcribe
billing_mode: owner_pays
billing_mode is user_pays (the safe-to-share default - each signed-in user spends their own credits) or owner_pays (you subsidize it; anonymous/logged-out callers and serverless functions work too). Billing is project-level, not per-deploy-target - one declaration applies to both dev and prod. This phase owns only the billing mode; finer per-service config (allowed models, providers, caps) is still set with project_settings and is preserved untouched. See app-llm for the billing-mode details.
No-sign-in app? Set
owner_payson every service it calls - billing is per-service. An anonymous/logged-out call to auser_paysservice returns401 LOGIN_REQUIRED(there's no signed-in user to bill). The common footgun: settingllm: owner_paysand leaving the rest at theuser_paysdefault, so text generation works but read-aloud (tts) /image/sound/musicsilently 401 for anonymous visitors. Grep your client/function code for/services/<name>to list everything the app calls, and add oneservice_definitionsline per service. (If you'd rather visitors pay, gate the feature behind "Sign in with Gipity" and keepuser_pays.) The 401 body carries ahintfield naming the offending service.
locationandnotifyare exempt from all of this: neither is credit-billed, so they need noservice_definitionsentry and never 401 an anonymous caller (notifysends are owner-billed automatically).service_definitionsentry (it's always owner-billed, neveruser_pays), though unlike those two it is credit-billed - one credit per recipient sent.
A records phase is the declarative, agent-free way to make a table publicly writable (e.g. a contact form) - the table is declared inline, not from a source dir:
- name: records
type: records
tables:
- table: contact_messages
auth_level: public # public | member (default) | user
searchable: false
Or flip one table without redeploying: gipity records config <table> --auth public.
A workflows phase makes automations part of the build loop: edit workflows/*.yaml, redeploy, and the workflow is created or updated in place (matched by name) and scheduled. See workflow for the YAML shape.
function_definitions
Functions auto-deploy as public endpoints, and the deploy pipeline auto-adds an entry to gipity.yaml for any function it finds undeclared. You only edit entries to grant more than the default:
| Field | Purpose |
|---|---|
name |
Function file name (without .js) |
auth |
public (no auth), user (logged-in), member (project member) |
tables |
Database tables the function may read/write |
fetch_domains |
External hosts the function may fetch - required to call app services like a.gipity.ai |
services |
Declared app services (llm, location, ...) - documentation only; services are still called via fetch |
auth_branding (optional) - style ONLY the "Sign in with Gipity" page
Scope: this styles only the Sign in with Gipity login/consent pages (server-rendered by Gipity). It does not theme your app's own UI - your app's look comes from your own CSS. So tagline / primary_color here change the sign-in screen, nothing else.
By default the sign-in page shows Gipity defaults (your project name, Gipity orange, the Gipity wordmark). Add a top-level auth_branding: block - a sibling of deploy:, not a phase - and gipity deploy syncs it to your project so that page matches your app:
version: 1
auth_branding: # styles the Sign in with Gipity page only
app_name: Lilyboxd # name shown ("Sign in to continue to Lilyboxd")
primary_color: "#fea60b" # accent for buttons/headings - MUST be quoted (see note)
logo_url: https://media.gipity.ai/.../logo.png # https only; replaces the Gipity wordmark
tagline: Your film diary # optional one-line subtitle
deploy:
phases:
- { name: files, type: static, source: src }
| Field | Rules |
|---|---|
app_name |
1-100 chars. Overrides the project name on the page. |
primary_color |
#RRGGBB hex only (no 3-digit, no alpha). |
logo_url |
https:// only, ≤2048 chars. Omit to show the Gipity wordmark. |
tagline |
≤120 chars. |
All fields optional - set only what you want to override; the rest fall back to Gipity defaults. It's declarative: the block is the source of truth, so it replaces the stored values on each deploy (delete a field to revert it to default). It's checksummed like a phase - unchanged blocks are skipped - and reported as an auth_branding line in the deploy results. A bad value (e.g. an invalid color) fails only the auth_branding step with a FIX gipity.yaml message; the rest of your deploy still lands.
Quote the hex color. In YAML a bare
#starts a comment, soprimary_color: #fea60bparses as empty. Always quote it:primary_color: "#fea60b".
Checksums - phases skip when unchanged
On re-deploy, a phase whose inputs haven't changed is skipped (you'll see it reported as unchanged). Use --force to re-run everything, or --only <phase> to target one phase.
The database phase verifies its checksums still describe the actual database: if the database was dropped, recreated, or emptied since the last deploy, the next deploy detects it, reports database was recreated - schema re-applied, and re-runs all migrations (and any configured seed: files) instead of skipping them.
Read the phase results - every deploy
Each phase reports a status. Do not move on until they're all clean:
| Status | Meaning | Action |
|---|---|---|
ok ✓ |
Phase succeeded | None |
warning ⚠ |
Deprecated or mis-shaped input (usually gipity.yaml); summary starts with "FIX" |
Treat as a TODO - fix the file and redeploy |
failed ✗ |
Phase did not complete | Fix before doing anything else - the deploy did not fully land |
A warning is not informational. Leaving it unresolved means something is already broken or about to be.
Related skills
- app-debugging - inspect the deployed page and read function logs
- app-development - writing the functions the pipeline deploys
- app-database - the database the pipeline migrates:
dbhelper, migrations, table permissions - app-testing - testing the deployed functions (
ctx.fn.call/callAs, the isolated test DB)