Preview Environments für jeden Pull Request mit der Sliplane API

Preview Environments für jeden Pull Request mit der Sliplane API

Jonas Scholz - Co-Founder von sliplane.ioJonas Scholz
7 min

Preview Environments klingen aufwendig, sind aber überraschend einfach umzusetzen. Die Idee: Jeder Pull Request bekommt seine eigene Live-URL, damit Reviewer die App direkt ausprobieren können – ohne den Branch lokal auschecken zu müssen.

In diesem Tutorial baust du einen GitHub Actions Workflow, der die Sliplane API nutzt, um beim Öffnen eines PRs automatisch ein Preview Environment zu erstellen, die URL als Kommentar zu posten und alles wieder aufzuräumen, wenn der PR geschlossen wird.

So funktioniert's

Der Workflow hat zwei Hauptjobs:

  1. PR geöffnet → Sliplane Service aus dem PR-Branch erstellen, warten bis er läuft, URL als PR-Kommentar posten
  2. PR geschlossen → Service anhand des Namens finden und löschen

Das läuft alles über die Sliplane API. Jeder öffentliche Service bekommt automatisch eine Managed Domain – du hast also sofort eine Live-URL.

Voraussetzungen

  • Ein Sliplane Account mit einem Server und Projekt
  • Deine App muss in einem GitHub Repository liegen und via Docker deploybar sein
  • Grundkenntnisse in GitHub Actions

Schritt 1: Sliplane API Token erstellen

Geh in die Sliplane Team Settings und erstell einen neuen API Token mit read-write Zugriff. Kopier ihn dir sofort – du siehst ihn nur einmal.

Schritt 2: Project ID und Server ID herausfinden

Du brauchst zwei IDs aus Sliplane:

  • Project ID — sichtbar in der URL, wenn du ein Projekt im Dashboard öffnest (z.B. project_abc123)
  • Server ID — sichtbar in der URL, wenn du einen Server öffnest (z.B. server_xyz456)

Du kannst sie auch direkt über die API abrufen:

# Alle Projekte auflisten
curl https://ctrl.sliplane.io/v0/projects \
  -H "Authorization: Bearer YOUR_API_TOKEN"

# Server in einem Projekt auflisten
curl https://ctrl.sliplane.io/v0/projects/PROJECT_ID/servers \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Schritt 3: Secret in GitHub hinterlegen

Geh in deinem Repository zu SettingsSecrets and variablesActions und füg folgendes hinzu:

SecretWert
SLIPLANE_API_TOKENDein Sliplane API Token

Schritt 4: Workflow erstellen

Leg die Datei .github/workflows/preview.yml in deinem Repository an:

.github/workflows/preview.yml
name: Preview Environment
on:
  pull_request:
    types: [opened, reopened, closed]

env:
  SLIPLANE_API: https://ctrl.sliplane.io/v0
  PROJECT_ID: your_project_id
  SERVER_ID: your_server_id

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - name: Create Preview
        if: github.event.action == 'opened' || github.event.action == 'reopened'
        id: create-preview
        run: |
          PREVIEW_NAME="preview-pr-${{ github.event.number }}"
          BRANCH="${{ github.head_ref }}"

          RESPONSE=$(curl -s -X POST "$SLIPLANE_API/projects/$PROJECT_ID/services" \
            -H "Authorization: Bearer ${{ secrets.SLIPLANE_API_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{
              "name": "'"$PREVIEW_NAME"'",
              "serverId": "'"$SERVER_ID"'",
              "deployment": {
                "url": "https://github.com/${{ github.repository }}",
                "branch": "'"$BRANCH"'",
                "autoDeploy": true
              },
              "network": {
                "public": true,
                "protocol": "http"
              }
            }')

          echo "service_id=$(echo "$RESPONSE" | jq -r '.id')" >> $GITHUB_OUTPUT
          echo "domain=$(echo "$RESPONSE" | jq -r '.network.managedDomain')" >> $GITHUB_OUTPUT

      - name: Wait for Service to be Ready
        if: github.event.action == 'opened' || github.event.action == 'reopened'
        run: |
          SERVICE_ID="${{ steps.create-preview.outputs.service_id }}"

          for i in {1..60}; do
            STATUS=$(curl -s "$SLIPLANE_API/projects/$PROJECT_ID/services/$SERVICE_ID" \
              -H "Authorization: Bearer ${{ secrets.SLIPLANE_API_TOKEN }}" \
              | jq -r '.status')

            echo "Attempt $i: Status is $STATUS"

            if [ "$STATUS" = "live" ]; then
              echo "Service is ready!"
              break
            elif [ "$STATUS" = "failed" ]; then
              echo "Service failed to start"
              exit 1
            fi

            if [ $i -eq 60 ]; then
              echo "Timeout waiting for service to be ready"
              exit 1
            fi

            sleep 5
          done

      - name: Comment Preview URL
        if: github.event.action == 'opened' || github.event.action == 'reopened'
        uses: actions/github-script@v7
        with:
          script: |
            const url = 'https://${{ steps.create-preview.outputs.domain }}';
            const body = `Preview deployed at: ${url}`;

            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
            const botComment = comments.find(c => c.body.includes('Preview deployed at:'));

            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body,
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body,
              });
            }

      - name: Delete Preview
        if: github.event.action == 'closed'
        run: |
          SERVICE_ID=$(curl -s "$SLIPLANE_API/projects/$PROJECT_ID/services" \
            -H "Authorization: Bearer ${{ secrets.SLIPLANE_API_TOKEN }}" \
            | jq -r '.[] | select(.name == "preview-pr-${{ github.event.number }}") | .id')

          if [ -n "$SERVICE_ID" ]; then
            curl -X DELETE "$SLIPLANE_API/projects/$PROJECT_ID/services/$SERVICE_ID" \
              -H "Authorization: Bearer ${{ secrets.SLIPLANE_API_TOKEN }}"
          fi

Ersetze your_project_id und your_server_id im env-Block mit deinen IDs aus Schritt 2.

Was die einzelnen Schritte machen

Create Preview — ruft POST /v0/projects/{projectId}/services auf und startet einen neuen Service aus deinem PR-Branch. Mit autoDeploy: true deployed Sliplane automatisch neu, wenn neue Commits auf den Branch gepusht werden. Die Response enthält eine managedDomain (z.B. preview-pr-42.sliplane.app), die wir an die nächsten Schritte weitergeben.

Du kannst dem Service auch Environment Variables oder Volumes mitgeben – dafür gibt es die Felder env und volumes im Service-Creation-Request. Schau in der API-Referenz nach dem vollständigen Schema.

Wait for Service — pollt GET /v0/projects/{projectId}/services/{serviceId} alle 5 Sekunden, bis der Status live ist. Bei failed bricht der Schritt sofort mit einem Fehler ab, nach 5 Minuten läuft ein Timeout.

Comment Preview URL — postet (oder aktualisiert) einen Kommentar am PR mit der Live-URL. Aktualisieren statt neu erstellen hält den PR übersichtlich, falls er geschlossen und wieder geöffnet wird.

Delete Preview — listet beim Schließen des PRs alle Services im Projekt auf, findet den passenden anhand des Namens und löscht ihn via DELETE /v0/projects/{projectId}/services/{serviceId}.

Schritt 5: Pull Request öffnen

Push den Workflow in deinen Main-Branch und öffne dann einen neuen PR. Im Actions-Tab siehst du den Workflow starten, und nach ein paar Minuten erscheint ein Bot-Kommentar am PR:

Preview deployed at: https://preview-pr-42.sliplane.app

Wenn du den PR mergst oder schließt, läuft der Cleanup-Job und der Service wird automatisch gelöscht.

Fazit

Das war's – vollautomatische Preview Environments mit etwa 80 Zeilen YAML und der Sliplane API. Jeder Reviewer bekommt eine Live-URL zum Ausprobieren, und du musst dir über Aufräumen keine Gedanken machen.

In der Sliplane API-Referenz findest du noch mehr: Environment Variables, Volumes, TCP/UDP-Services und mehr. Wenn du eigene Domains statt der Managed Domains willst, geht das auch – über POST /v0/projects/{projectId}/services/{serviceId}/domains.

Cheers,

Jonas

Willkommen in der Container-Cloud

Sliplane macht es einfach, Container in der Cloud zu deployen und bei Bedarf zu skalieren. Probier es jetzt aus!