Skip to content

Deploy to a PaaS

PaaS providers have built-in GitHub integrations but no native Codebahn connection. You deploy by running the provider’s CLI in your CI workflow, the same way you would run tests. To get deploy status back on your commits and pull requests, you create a Codebahn environment and point the provider’s webhook at its callback URL.

The deploy steps below are ordinary CI. The Codebahn-specific part is the environment callback: when the provider POSTs a status to the callback URL you configured, Codebahn writes a commit status and, for previews, a PR comment. See environments for the callback mechanism, payload formats, and security model.

  1. Add an environment in the repository’s Environments tab (name + provider). Codebahn gives you a callback URL with a secret token.
  2. Add the provider’s deploy CLI command to your CI workflow.
  3. In the provider’s settings, add a webhook pointed at the callback URL.
  4. On push, CI runs your tests and deploys via the CLI. When the provider POSTs to the callback URL, Codebahn writes the commit status.

The environment callback is optional. If you skip it, the commit status comes from the CI step’s exit code instead, which is enough when the deploy runs entirely inside your workflow.

The workflow snippets are generic CI. Add provider secrets under Settings > Actions > Secrets (stock Forgejo; see the upstream docs). Jobs run on hosted EU runners, so any CLI you reference is installed in the step, not pre-baked into the image. See CI runners for what’s already on the image.

Secrets:

  • VERCEL_TOKEN from vercel.com/account/tokens
  • VERCEL_ORG_ID and VERCEL_PROJECT_ID from .vercel/project.json (run vercel link locally)

Workflow:

deploy-production:
runs-on: ubuntu-latest
needs: check
if: github.ref == 'refs/heads/main'
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
- run: npm i -g vercel
- run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}
deploy-preview:
runs-on: ubuntu-latest
needs: check
if: github.event_name == 'pull_request'
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
- run: npm i -g vercel
- name: Deploy preview
id: deploy
run: |
url=$(vercel deploy --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> "$GITHUB_OUTPUT"
- name: Comment preview URL
run: |
curl -s -X POST \
-H "Authorization: token ${{ secrets.CODEBAHN_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"Preview: ${{ steps.deploy.outputs.url }}\"}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments"

The preview job posts the deploy URL as a PR comment via the API. CODEBAHN_TOKEN is a personal access token you create and add as an Actions secret yourself; it is not injected for you. If you set up the Vercel callback below, the environment posts its own preview comment and you can drop this step.

Callback: Create a webhook in Vercel > Project Settings > Webhooks pointed at the callback URL from your environment. Select deployment.succeeded, deployment.error, deployment.created, deployment.canceled. Vercel signs callbacks; paste the signing secret into the environment.

Secrets:

  • NETLIFY_AUTH_TOKEN from app.netlify.com
  • NETLIFY_SITE_ID from Site configuration > General

Workflow (build in CI, upload output):

deploy:
runs-on: ubuntu-latest
needs: check
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: pnpm install && pnpm build
- run: npx netlify-cli deploy --dir=./dist --prod --auth=${{ secrets.NETLIFY_AUTH_TOKEN }} --site=${{ secrets.NETLIFY_SITE_ID }}

Callback: In Site configuration > Notifications > Outgoing webhooks, add “Deploy succeeded” and “Deploy failed” pointed at the callback URL.

Secrets:

  • RAILWAY_TOKEN from project settings (project-scoped token)

Workflow:

deploy:
runs-on: ubuntu-latest
needs: check
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm i -g @railway/cli
- run: railway up --detach
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

Callback: In Project Settings > Webhooks, add the callback URL and select deployment events. Railway does not sign callbacks; the secret token in the callback URL is the authentication.

Secrets:

  • CLOUDFLARE_API_TOKEN with “Cloudflare Pages: Edit” permission
  • CLOUDFLARE_ACCOUNT_ID from the Cloudflare dashboard

Workflow (build in CI, upload output):

deploy:
runs-on: ubuntu-latest
needs: check
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: pnpm install && pnpm build
- run: npx wrangler pages deploy ./dist --project-name=my-project
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Cloudflare Pages can send deploy alerts as a dashboard notification (see environments). If you skip that, the commit status comes from the CI step’s exit code.

Render does not deploy from source without a connected git repo. Two options:

Option A: Docker image. Build and push an image to your Codebahn registry in CI, then trigger Render. The built-in token pushes to your own owner with no secret to manage; see Publish container images.

deploy:
runs-on: ubuntu-latest
needs: check
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: echo "${{ github.token }}" | docker login codebahn.net -u "${{ github.actor }}" --password-stdin
- run: docker build -t codebahn.net/${{ github.repository }}:${{ github.sha }} .
- run: docker push codebahn.net/${{ github.repository }}:${{ github.sha }}
- run: |
curl -X POST "https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys" \
-H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"imageUrl":"codebahn.net/${{ github.repository }}:${{ github.sha }}"}'

Images are private, so give Render a read:package token as a registry credential to pull. See pull from outside Codebahn. Keeping the image on Codebahn rather than a US registry holds the no-US-vendor line.

Callback: In Settings > Webhooks (a paid Render plan is required; check your plan), add the callback URL.

Push a commit. Open the repository’s Environments tab and watch for callback events as the provider processes the deploy, then check the commit for its status.