GitLab and Bitbucket
Deploy from GitLab or Bitbucket in one of two ways. Deplane never holds a credential that reaches more than one repository.
- From your pipeline (GitLab CI or Bitbucket Pipelines): your job runs
deplane deploy, proving who it is with the short ID token its CI host signs for each job. Deplane stores no credential at all. - Connect a repository: like GitHub, every push deploys and merge or pull requests get previews, with no pipeline file. You paste a token that reaches one repository only.
From your pipeline
Trust the repository
An app trusts a repository's jobs to deploy it. In the app's Settings, under Deploy from GitLab CI or Bitbucket Pipelines, or with the CLI:
deplane pipelines trust gitlab --app web --project 123456 --branch main
deplane pipelines trust bitbucket --app web --workspace '{workspace-uuid}' --repository '{repository-uuid}' --name acme/web --branch main
The repository is named by its IDs, never its path, because a path can be renamed and taken by someone else. GitLab shows the project ID on the project's page. Bitbucket shows the UUIDs in Repository settings, OpenID Connect.
The deploy branch deploys the app. Any other branch can only deploy previews, unless you pass --no-previews. With --preview-database, each preview gets its own empty database, as GitHub previews can (free, at most 3 per team, deleted with the preview). Adding a trust asks for your passkey, as creating a CI token does. An app can trust at most 3 repositories. deplane pipelines list shows each trust with its last use, and deplane pipelines remove ID stops it at once.
GitLab CI
The job asks GitLab for an ID token whose audience is your Deplane API URL (https://api.deplane.io):
deploy:
image: alpine
id_tokens:
DEPLANE_ID_TOKEN:
aud: https://api.deplane.io
script:
- deplane deploy --app web
rules:
- if: $CI_COMMIT_BRANCH == "main"
preview:
image: alpine
id_tokens:
DEPLANE_ID_TOKEN:
aud: https://api.deplane.io
script:
- deplane deploy --app web --preview "$CI_MERGE_REQUEST_IID"
environment:
name: review/$CI_MERGE_REQUEST_IID
on_stop: stop_preview
auto_stop_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
stop_preview:
image: alpine
id_tokens:
DEPLANE_ID_TOKEN:
aud: https://api.deplane.io
script:
- deplane previews remove --app web "$CI_MERGE_REQUEST_IID"
environment:
name: review/$CI_MERGE_REQUEST_IID
action: stop
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
GitLab runs the stop job when the merge request is merged or closed.
Bitbucket Pipelines
A step with oidc: true gets BITBUCKET_STEP_OIDC_TOKEN. Give it your API URL as its audience:
pipelines:
branches:
main:
- step:
oidc:
audiences: [https://api.deplane.io]
script:
- deplane deploy --app web
pull-requests:
'**':
- step:
oidc:
audiences: [https://api.deplane.io]
script:
- deplane deploy --app web --preview "$BITBUCKET_PR_ID"
custom:
stop-preview:
- variables:
- name: PR_ID
- step:
oidc:
audiences: [https://api.deplane.io]
script:
- deplane previews remove --app web "$PR_ID"
Run stop-preview from a pullrequest-fulfilled or pullrequest-rejected trigger.
What a pipeline can do
Deplane checks each ID token:
- its signature: RS256, with the CI host's own keys;
- the issuer and the audience (your API URL);
- its age;
- the repository's IDs: on GitLab the project of the pipeline and of the job, so a fork's merge request never matches;
- the branch.
It then answers with a pipeline token that lasts 15 minutes. That token belongs to the trust, not to a person, and keeps working after whoever added the trust leaves the team. Every refusal is pipeline_not_trusted; the app's activity log says why.
From the deploy branch, a pipeline uploads the folder and follows its build and release. From other branches, it deploys and removes the app's previews. It cannot roll back, change variables or scaling, see tokens, or reach other apps. A pipeline never creates the app or changes its settings: deploy it once yourself first. Previews get the app's variables except DATABASE_URL and the other database addresses. A preview not deployed for 7 days is deleted, in case a stop job never ran.
The CLI never prints the ID token, and --debug hides it.
Connect a repository
GitLab
- On your own GitLab account, make a fine-grained personal access token with "Group and project access" set to this one project, an expiry within 90 days, and exactly these permissions, no others (no Push):
| Tab | Category | Resource | Action |
|---|---|---|---|
| Group and project | Projects | Project | Read |
| Group and project | Repository | Repository | Read |
| Group and project | Repository | Branch | Read |
| Group and project | Repository | Commit | Read |
| Group and project | Repository | Merge Request | Read |
| Group and project | Project Planning | Work Item | Read, Create, Update |
| Group and project | CI/CD | Commit Status | Create |
| User | System Access | Personal Access Token | Read, Rotate, Revoke |
Your account needs Developer or higher in the project. The token's permissions, not your role, keep Deplane from pushing.
- In the app's Settings, under GitLab, enter the project ID and branch, and paste the token. Deplane checks it, seals it, and deploys the branch.
- In GitLab, under Settings › Webhooks, add the URL Deplane shows, with the Push and Merge request events. Paste the webhook's signing token back into Settings.
Deplane renews the token every 7 days. Commit statuses and the one merge request comment are posted as you, so each starts with "Deplane:".
Bitbucket
- In the repository's settings, under Access tokens, make a repository access token with only Repositories: Read and Pull requests: Read.
- In the app's Settings, under Bitbucket, enter the repository as
workspace/repositoryand the branch, and paste the token. - Deplane shows a webhook URL and a secret, once. In Bitbucket, under Repository settings › Webhooks, add them with the Repository push and the Pull request created, updated, merged and declined events.
Bitbucket does not let Deplane read the token's expiry: deploys stop when it expires.
What a connection does
- A push to the branch deploys its latest commit. A webhook only tells Deplane to look: Deplane asks the host for the branch's head and builds that, so an old or forged delivery cannot deploy anything else. Only signed deliveries count.
- Merge and pull requests get previews. Requests from forks get none, because their code would run with the app's variables. A comment says why instead.
- The files are those of
deplane deploy, with your.gitignoreand.dockerignore. - If the host refuses the token, or its permissions or projects change, Deplane disconnects the app, deletes its previews from this connection, shows why in Settings, and emails the team's owners.
deplane gitlab and deplane bitbucket show a connection; deplane gitlab deploy and deplane bitbucket deploy deploy the branch's head now.
Disconnect
In the app's Settings. On GitLab, Deplane revokes the token and forgets it. Bitbucket offers no way to revoke a token from outside: Deplane forgets its copy at once, and asks you to delete the token and the webhook on Bitbucket, then to confirm. Either way the app keeps running its current release.
What Deplane stores
- Pipelines: the trusts (repository IDs, branch, whether previews are on). No credential.
- Connections: the token, sealed with the platform's keyring, opened only to talk to the host, and never logged or shown. The webhook's secret, sealed too. For display: the repository's path, the token's name and its expiry.
This page is also available as Markdown. AI agents can start at /llms.txt.