# Export your source to GitHub

Initialize an empty GitHub repository with the source Volcano stores for your project, then deploy it from Git.

Export creates the first commit in an empty GitHub repository. It pushes the
complete project directly to the configured production branch, which triggers
the normal Git deployment flow.

```bash
curl -X POST "https://api.volcano.dev/projects/$PROJECT_ID/source-export" \
  -H "Authorization: Bearer $VOLCANO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"production_branch":"main","repo_id":873345021}'
```

```json
{
  "repo_full_name": "acme/storefront",
  "branch": "main",
  "commit_sha": "6f2b1c9a4e8d3f7b0a5c2e9d1f4b8a6c3e7d0b25",
  "file_count": 12,
  "skipped": [],
  "omitted": [{ "kind": "migrations", "resource": "", "path": "volcano/migrations" }]
}
```

## Prepare the repository

1. Create a GitHub repository without a README, license, `.gitignore`, or any
   other initial commit.
2. Connect it to the project and export from the
   [CLI](https://docs.volcano.dev/cli/git.md). An empty repository has no default branch, so name the
   branch pushes will deploy from when you connect it:

   ```bash
   volcano git connect https://github.com/acme/storefront --production-branch main
   volcano git export
   ```

   `volcano git export` shows the repository and production branch and asks
   before it writes. In a script, pass the branch you expect and skip the
   prompt; the command refuses if the project's branch is different:

   ```bash
   volcano git export --branch main --yes --json
   ```

`volcano git repos` lists the repositories you can connect and marks the empty
ones.

For API clients, connect the repository and configure Git auto-deploy first.
Read `repo_full_name`, `repo_id` and `production_branch` from
`GET /projects/{id}/git-connection`, show the repository and branch for
confirmation, then send `repo_id` and `production_branch` to the export
endpoint. If either changed in the meantime, export returns `409` without
writing the repository. Read the connection again and ask the user to confirm
what it holds now. `repo_id` is optional, but without it export writes to
whatever repository is connected when the request arrives.

Export also returns `409` while a function or frontend deployment is queued or
provisioning. Wait for that deployment to finish, then export again. It returns
`409` too while a [preview](https://docs.volcano.dev/platform/frontends/previews.md) is serving any of the
project's frontends, because the repository would not hold the source that
frontend serves. Roll each such frontend back to its own build, then export
again. Volcano checks both before creating or pushing the root commit.

Volcano does not create the repository. Export refuses any repository that
already has a commit or branch. This avoids merges, conflict resolution, and
overwriting existing history.

If your repository already contains the project, do not export. Push the
complete project to its configured production branch instead. Git auto-deploy
will deploy it, but it does not disable direct platform deploys. Source
ownership changes only through the one-time export flow.

## What export writes

The root commit uses the layout [Git auto-deploy](https://docs.volcano.dev/platform/projects/git-deploy.md#repository-layout)
discovers:

```text
volcano/
├── .gitignore
└── functions/
    ├── hello.js
    └── checkout/
        ├── index.js
        └── tax.js
volcano-config.yaml
package.json
frontends/
├── web/
│   └── package.json
└── admin/
    └── package.json
.volcano/
└── source-layout.json
```

Export supports multiple frontends and functions. Each frontend gets a
`frontends/<name>/` workspace, including its dependencies and original app root.
Functions keep the existing single-file or directory layout.

The generated `.volcano/source-layout.json` records frontend names and app roots.
Keep it in Git: it tells Volcano to deploy every exported frontend instead of
using the single frontend in Git deploy settings. The Auto deploy and Deploy
functions switches still apply.

The initial export and every later production-branch push deploy all configured
resources, including those whose code has not changed. Git takes over source
ownership only after a deployment covers every exported serving resource.

Export takes source from each function and frontend's latest successful
deployment. A resource that has never deployed successfully appears in
`skipped` and is not written.

Both kinds of function are exported into the same `volcano/functions/`
directory. Durable functions are marked with `kind: durable` in
`volcano-config.yaml`, which is how the first push from the exported repository
recreates them as durable rather than standard.

Export refuses to push when a currently serving resource has no readable stored
source or cannot be represented in the repository layout. This keeps the first
Git deployment from waiting on source that the commit does not contain.

Stored `.gitignore` files are committed as ordinary files. Their patterns do
not remove other stored files from the export commit.

The `omitted` list reports content Volcano cannot or should not publish:

| Kind                | Why                                                                  |
| ------------------- | -------------------------------------------------------------------- |
| `migrations`        | Volcano does not retain the migration files.                         |
| `variable_values`   | Secret values are never committed. Configure them through Volcano.   |
| `ignored_secret`    | Credential-shaped files such as `.env` are excluded.                 |
| `dependency_cache`  | Generated dependency directories such as `node_modules` are rebuilt. |
| `unsupported_entry` | Symlinks and other unsupported archive entries are excluded.         |

Export fails and names the path when stored source contains a file that should
never be committed, such as a private key or credential store.

## How ownership changes

Export is a one-time transition:

1. Volcano verifies the confirmed repository and production branch still
   match the project, pins the branch, and changes the project to
   `git_exporting`. Direct source deploys and Git connection changes are
   frozen.
2. Volcano creates one root commit and pushes it to the production branch.
3. GitHub's signed push event confirms that the root commit reached the
   production branch. That commit or a newer production push starts the
   ordinary Git deployment and changes the mode to `git_pending`.
4. After a complete production deployment succeeds, the mode becomes `git`.

The project stays locked if the deployment fails, or if a function holds only
TypeScript, which [isn't deployed](https://docs.volcano.dev/platform/projects/git-deploy.md). Fix the
repository and push the production branch again. The latest complete successful deployment
finishes the transition; the exported root commit does not need to deploy if a
newer push supersedes it.

The pinned branch does not follow later GitHub default-branch changes. This
keeps a repository rename from moving production during or after handover.
After handover you can
[return it to the default](https://docs.volcano.dev/platform/projects/git-deploy.md#deploy-from-a-different-branch) with an
empty `production_branch`.

To stop an incomplete transition and restore direct platform deploys, run
`volcano git export cancel`, or call the API:

```bash
curl -X DELETE "https://api.volcano.dev/projects/$PROJECT_ID/source-export" \
  -H "Authorization: Bearer $VOLCANO_TOKEN"
```

Cancellation does not delete the repository or a commit that already reached
it. If Volcano reserved the initial commit before cancellation, the one-time
export remains consumed and cannot be run again. The confirmed production
branch remains pinned. Once the repository has its first commit, send an empty
`production_branch` to
[follow it again](https://docs.volcano.dev/platform/projects/git-deploy.md#deploy-from-a-different-branch).

Check the state at any time. `volcano git status` reports it as the project's
source of truth; the API returns it as `mode`:

```bash
curl "https://api.volcano.dev/projects/$PROJECT_ID/source-export" \
  -H "Authorization: Bearer $VOLCANO_TOKEN"
```

```json
{
  "mode": "git_pending",
  "transition_started_at": "2026-08-25T09:14:02Z",
  "exported_at": "2026-08-25T09:14:04Z",
  "handed_over_at": null
}
```

After handover, push to the production branch to change or deploy source.
Direct function and frontend deploys return `409`. Variables, databases,
storage, domains, and resource settings remain manageable through Volcano.

To deploy from another branch after handover, set it explicitly, then push it:

```bash
curl -X PUT "https://api.volcano.dev/projects/$PROJECT_ID/git-connection/production-branch" \
  -H "Authorization: Bearer $VOLCANO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"production_branch":"release"}'
```

Branch changes are refused while the project is `git_exporting` or
`git_pending` so the initial push and deployment always use the branch the user
confirmed.

## Retry an uncertain push

If GitHub does not confirm whether the initial push succeeded, the endpoint
returns a retryable error and the project remains `git_exporting`. Call the
endpoint again, or rerun `volcano git export`. Volcano builds the same commit:
it adopts that commit if it is already on the production branch, or retries the
initial push while the repository is still empty.

If the commit reached GitHub but its push event never reaches Volcano, the
project remains `git_exporting`. Cancel the transition with
`volcano git export cancel` or `DELETE /projects/{id}/source-export` to restore
platform deploys. The root commit stays
in the repository and the one-time export remains consumed.

## Related

| Guide                                      | Description                                   |
| ------------------------------------------ | --------------------------------------------- |
| [Deploy from GitHub](https://docs.volcano.dev/platform/projects/git-deploy.md)        | Connect a repository and deploy on every push |
| [Configuration manifest](https://docs.volcano.dev/platform/projects/configuration.md) | `volcano-config.yaml` reference               |
