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_pays on every service it calls - billing is per-service. An anonymous/logged-out call to a user_pays service returns 401 LOGIN_REQUIRED (there's no signed-in user to bill). The common footgun: setting llm: owner_pays and leaving the rest at the user_pays default, so text generation works but read-aloud (tts) / image / sound / music silently 401 for anonymous visitors. Grep your client/function code for /services/<name> to list everything the app calls, and add one service_definitions line per service. (If you'd rather visitors pay, gate the feature behind "Sign in with Gipity" and keep user_pays.) The 401 body carries a hint field naming the offending service.

location and notify are exempt from all of this: neither is credit-billed, so they need no service_definitions entry and never 401 an anonymous caller (notify sends are owner-billed automatically). email likewise needs no service_definitions entry (it's always owner-billed, never user_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, so primary_color: #fea60b parses 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