# Cloud as source
URL: https://openship.io/docs/architecture/cloud-as-source.md

Cloud projects are owned by Openship Cloud; your self-hosted instance is a gateway in front of it — plus how projects promote up and come back home.

For a **Cloud-owned project**, Openship Cloud (our hosted service) holds the one true copy — the
**canonical** owner. Its record, deployments, domains, environment variables, and logs all live on the
cloud, and the cloud runs its builds and lifecycle. Your self-hosted instance keeps **no local copy** of
that project. Instead it acts as a **gateway** — a thin pass-through that forwards requests for that project
up to the cloud and hands you back the answer.

Think of it like a mailbox on your street. The letters aren't stored in the mailbox; the mailbox just
carries them to and from the real post office. Your instance is the mailbox; Openship Cloud is the post
office.

<Callout title="One rule to remember">
A project lives in **exactly one place** — fully local, or fully on the cloud. Never half in each. That is
the whole idea behind this page.
</Callout>

## A gateway, not a mirror

Openship deliberately never keeps two copies of the same project. A mirror would mean two sources of truth
that constantly drift apart and need reconciling — and a bug on your box could corrupt cloud data. A gateway
avoids all of that: there is nothing to sync, because there is only ever one copy.

Your dashboard can keep talking to your local API. Requests for a Cloud project are forwarded
upstream and returned through the gateway. SDK and CLI credentials fixed to an organization
must connect directly to that organization's API; a local scoped credential cannot borrow
its owner's Cloud session.

### Creating a project from Desktop

In the connected dashboard, selecting a managed Cloud server for a new catalog app or
Git project creates its configuration through the Cloud API. Capacity checks, installation
and subsequent project operations use that Cloud account and organization. The project,
services and routes are stored in Cloud; the desktop controller keeps no project copy.
An existing verified server link is translated to its canonical Cloud server ID.

Local folders, upload sessions and custom app definitions uploaded to this instance keep
their local controller because their source is available there. Existing local projects retain
their owner, even when they run on a linked managed server. Use an explicit transfer to move an existing project's ownership.
Credentials scoped to the local installation keep their existing execution path; connect
SDK and CLI clients directly to the Cloud API to create Cloud-owned projects.

A failed Cloud creation or capacity check remains an error. Openship does not fall back
to creating a local project or selecting a different Cloud account.

<Callout title="Your local permission checks still run first" type="info">
Forwarding does **not** skip your rules. The local permission plane gates every call — checking the caller's
role and access — *before* anything is proxied. Only then does the gateway forward the request.
</Callout>

## How the gateway knows where a project lives

For every project-scoped request, the local API decides "serve this from my own database, or forward it to
the cloud?" in this order:

1. **A local lookup.** A project ID present in the local database stays local, including
   a row owned by another tenant. Local authorization still decides whether it is accessible.
2. **A cloud link.** No local row, but the caller's organization has a linked Cloud account?
   The request may be forwarded after local permission and credential checks.
3. **Cloud authorization.** Cloud validates that the requested ID belongs to the linked
   Cloud organization. An unknown or foreign ID remains a `404`.
4. **Otherwise, not found.** Without a link, an unknown ID is handled locally and returns `404`.

A client-provided `cloud` source hint cannot override a local record or grant Cloud access.
An explicit `local` hint can keep project requests on the local path.

If the cloud can't be reached while forwarding, you get a `503` with code `CLOUD_UNREACHABLE` — not a `404`.
That distinction matters: the project *does* exist, the cloud is just temporarily out of touch.

## Actions for Cloud-owned apps

Opening a Cloud project's **Actions** tab forwards its workflows, runs and deployment
rules to Cloud. Workflow and run detail requests, log cursors, artifacts and runner
changes follow the same ownership decision. The Actions workflow, project and runner
lists include both local and connected Cloud inventory; no Cloud records are copied
into your local database.

A workflow's projects and runners must belong to the same instance. An import or
policy that mixes local and Cloud targets fails before either side writes anything.
Cloud checks its own repository access, destinations and approval policy, and records
the mutation there. Local credentials scoped to this instance cannot borrow the
owner's Cloud session. Connect your SDK or CLI directly to Cloud when using a fixed
Cloud organization scope.

If Cloud cannot be reached, Actions reports a connection error instead of presenting
an empty list. Local project requests continue to use the local engine. On Cloud
itself, these operations run directly and never proxy back to Cloud.

## One cloud identity for the whole team

A cloud connection belongs to the **organization owner**, not to each person. When the gateway forwards a
call, it authenticates as the owner's Openship Cloud session (read server-side from encrypted storage — no
cookies or tokens ever travel from the browser).

So teammates do **not** each need their own cloud account. Your local instance decides *who is allowed* to
act (by their role), then proxies the action as the owner. Because the connection is org-owned, every member
sees the same "connected" state and the same list of cloud workspaces.

## Promote: sending a project up to the cloud

**Promote** moves a local project onto Openship Cloud so the cloud becomes its canonical home. Under the
hood it copies the project's whole **subgraph** (the project row plus everything attached to it —
deployments, domains, env, and so on) up to the cloud, and then removes the local rows so no shadow copy is
left behind.

There are two ways it happens:

- **Born on Cloud.** New catalog apps and Git projects created from the connected dashboard with a
  managed Cloud server are created directly in Cloud. There is no local project to copy or delete.
- **Explicit transfer.** You can also promote on demand from the CLI or API (see
  [Doing it yourself](#doing-it-yourself)).

<Callout title="Auto-deploy follows the project up" type="info">
Promote keeps the project's GitHub webhook. After promoting, a `git push` to the repo is forwarded to the
cloud copy and redeploys it *there* — so auto-deploy keeps working without any change on your side.
Bring-home reverses this: pushes are handled locally again.
</Callout>

<Callout title="Locally-encrypted secrets aren't carried up as-is" type="warn">
Values encrypted with **your instance's** key (secret env vars) can't be decrypted by the cloud, so they're
stripped during promote. After a project is on the cloud, re-enter those secrets in the cloud project.
</Callout>

## Bring home: pulling a project back

**Bring-home** is the reverse: it exports the project's subgraph from the cloud, restores it into your local
database, clears the project's cloud link, and then tears down the leftover rows on the cloud side so a
future promote won't collide. From that moment the project is canonical-local again, and pushes to its
GitHub repo are handled locally instead of forwarded.

<Callout title="What bring-home moves today">
Bring-home moves the project's **data**. Reconnecting things like custom-domain DNS and bringing the app's
containers back up are operator follow-up steps, not part of the data transfer itself.
</Callout>

## What your box still does for a cloud project

For a Cloud-owned project, your local instance forwards operations to Cloud. A local project can
also use a verified managed server as its execution destination: the local controller owns its
configuration and orchestration, while the managed server and its billing belong to Cloud.
This supports local-folder sources and credentials scoped to the installation without giving
them the owner's Cloud account permissions. See [Runtime model](/docs/architecture/runtime-model)
for how a runtime target is chosen.

## On Openship Cloud itself

There's a neat symmetry here: **on the cloud service, every project is already canonical-local** — there is
nothing upstream to forward to. The exact same code runs on both sides; the gateway simply resolves
"local" when it *is* the cloud, so no request ever loops back on itself.

## Doing it yourself

Promote and bring-home are self-hosted-only actions, guarded by the `project:admin` permission. Your
organization also has to be **connected to Openship Cloud** first — a transfer on an unconnected org is
refused.

```bash
# Promote a local project up to Openship Cloud
openship project transfer <projectId> to-cloud

# Bring a cloud project back to this instance
openship project transfer <projectId> to-self-hosted
```

The matching API routes are `POST /api/projects/:id/transfer/to-cloud` and
`POST /api/projects/:id/transfer/to-self-hosted`. On promote, a transfer that copies up cleanly but can't
finish local cleanup returns a partial-success (`207`) so you can retry the cleanup — the cloud copy is
already authoritative.

<Callout title="Errors a transfer can return" type="warn">
- **`412`** — your org isn't connected to Openship Cloud yet.
- **`409`** — the project is already on the target side, *or* a leftover cloud copy / a name clash blocks the
  promote. Openship never overwrites the other copy; it asks you to clean up or rename and retry.
- **`502`** — the cloud accepted the call but the transfer itself failed upstream.
</Callout>

## Related guides

<Cards>
  <Card title="Data ownership" href="/docs/architecture/data-ownership" description="Exactly what lives where — your database vs. the cloud." />
  <Card title="Runtime model" href="/docs/architecture/runtime-model" description="How a deployment resolves to Docker, a bare process, or the cloud." />
  <Card title="Architecture overview" href="/docs/architecture/overview" description="How the pieces of Openship fit together." />
</Cards>
