diff --git a/.gitea/workflows/branch-cleanup.yaml b/.gitea/workflows/branch-cleanup.yaml
new file mode 100644
index 0000000..f7189d3
--- /dev/null
+++ b/.gitea/workflows/branch-cleanup.yaml
@@ -0,0 +1,14 @@
+name: Remove branch deployment from branch.dsv.su.se
+on:
+  pull_request:
+    types:
+      - closed
+jobs:
+  cleanup:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: https://gitea.dsv.su.se/ansv7779/action-branch-deploy@v2
+        with:
+          cleanup-ssh-key: ${{ secrets.BRANCH_CLEANUP_KEY }}
+          compose-file: branch-compose.yaml
+          mode: 'cleanup'
diff --git a/.gitea/workflows/branch-deploy.yaml b/.gitea/workflows/branch-deploy.yaml
new file mode 100644
index 0000000..dad0a0d
--- /dev/null
+++ b/.gitea/workflows/branch-deploy.yaml
@@ -0,0 +1,27 @@
+name: Deploy to branch.dsv.su.se
+on:
+  - pull_request
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+      - id: deploy
+        uses: https://gitea.dsv.su.se/ansv7779/action-branch-deploy@v2
+        with:
+          ssh-key: ${{ secrets.BRANCH_DEPLOY_KEY }}
+          compose-file: branch-compose.yaml
+          mode: 'deploy'
+      - name: Post URL to deployment as comment
+        uses: actions/github-script@v7
+        if: github.event.action == 'opened'
+        env:
+          BRANCH_URL: ${{ steps.deploy.outputs.url }}
+        with:
+          script: |
+            const url = process.env.BRANCH_URL;
+            github.rest.issues.createComment({
+              issue_number: context.issue.number,
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              body: `Deployed to ${url}`
+            })
diff --git a/bff/pom.xml b/bff/pom.xml
index 016c7c3..7303115 100644
--- a/bff/pom.xml
+++ b/bff/pom.xml
@@ -14,6 +14,7 @@
     <groupId>se.su.dsv.studentportalen</groupId>
     <artifactId>bff</artifactId>
     <version>1.0-SNAPSHOT</version>
+    <packaging>war</packaging>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/bff/src/main/resources/application-branch.yaml b/bff/src/main/resources/application-branch.yaml
new file mode 100644
index 0000000..548cd8c
--- /dev/null
+++ b/bff/src/main/resources/application-branch.yaml
@@ -0,0 +1,7 @@
+spring.security.oauth2.client.provider.dsv.issuer-uri: ${OAUTH2_ISSUER_URI}
+spring.security.oauth2.client.registration.studentportalen.client-id: ${OAUTH2_CLIENT_ID}
+spring.security.oauth2.client.registration.studentportalen.client-secret: ${OAUTH2_CLIENT_SECRET}
+se.su.dsv.backend-api.daisy-url: ${BACKEND_API_DAISY}
+se.su.dsv.frontend.url: ${FRONTEND_URL}
+
+server.forward-headers-strategy: framework
diff --git a/branch-bff.Dockerfile b/branch-bff.Dockerfile
new file mode 100644
index 0000000..4be8b54
--- /dev/null
+++ b/branch-bff.Dockerfile
@@ -0,0 +1,30 @@
+FROM eclipse-temurin:24 AS build
+
+WORKDIR /build
+
+COPY mvnw mvnw
+COPY .mvn/ .mvn/
+COPY pom.xml pom.xml
+
+RUN ./mvnw dependency:go-offline \
+    --batch-mode \
+    --fail-fast
+
+COPY src/ src/
+
+RUN ./mvnw package \
+    --batch-mode \
+    --fail-fast \
+    -DskipTests
+
+FROM eclipse-temurin:24 AS runtime
+
+WORKDIR /bff
+
+COPY --from=build /build/target/*.war ./bff.war
+
+ENV SPRING_PROFILES_ACTIVE=branch
+
+EXPOSE 8080
+
+ENTRYPOINT ["java", "--enable-preview", "-jar", "bff.war"]
diff --git a/branch-compose.yaml b/branch-compose.yaml
new file mode 100644
index 0000000..8e8db2c
--- /dev/null
+++ b/branch-compose.yaml
@@ -0,0 +1,68 @@
+services:
+  frontend:
+    build:
+      context: ./frontend
+      dockerfile: ../branch-frontend.Dockerfile
+      args:
+        BACKEND_URL: https://bff-${VHOST}
+    restart: unless-stopped
+    networks:
+      - traefik
+    environment:
+      VITE_BACKEND_URL: https://bff-${VHOST}
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.routers.frontend-${COMPOSE_PROJECT_NAME}.rule=Host(`${VHOST}`)"
+      - "traefik.http.routers.frontend-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
+
+  bff:
+    build:
+      context: ./bff
+      dockerfile: ../branch-bff.Dockerfile
+    restart: unless-stopped
+    networks:
+      - internal
+      - traefik
+    environment:
+      OAUTH2_ISSUER_URI: https://oauth2-${VHOST}
+      OAUTH2_CLIENT_ID: studentportalen
+      OAUTH2_CLIENT_SECRET: p4ssw0rd
+      FRONTEND_URL: https://${VHOST}
+      BACKEND_API_DAISY: http://mock-apis:8080/daisy
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.routers.bff-${COMPOSE_PROJECT_NAME}.rule=Host(`bff-${VHOST}`)"
+      - "traefik.http.routers.bff-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
+
+  oauth2:
+    build: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#main #branch=main, it defaults to master
+    restart: unless-stopped
+    networks:
+      - traefik
+    environment:
+      CLIENT_ID: studentportalen
+      CLIENT_SECRET: p4ssw0rd
+      CLIENT_SCOPES: openid profile email offline_access
+      CLIENT_REDIRECT_URI: https://bff-${VHOST}/login/oauth2/code/studentportalen
+    labels:
+      - "traefik.enable=true"
+      - "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.rule=Host(`oauth2-${VHOST}`)"
+      - "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
+
+  mock-apis:
+    build: https://gitea.dsv.su.se/DMC/apimposter.git#main #branch=main, it defaults to master
+    restart: unless-stopped
+    networks:
+      - internal
+    environment:
+      MOCK_BASE_PATH: / # HTTP base path for the mock server
+      MOCK_FILE_PATH: /mocks
+    volumes:
+      - ./bff/src/mock-api:/mocks
+
+networks:
+  internal:
+    name: ${COMPOSE_PROJECT_NAME}_internal
+  traefik:
+    name: traefik
+    external: true
diff --git a/branch-frontend.Dockerfile b/branch-frontend.Dockerfile
new file mode 100644
index 0000000..6f1ebfd
--- /dev/null
+++ b/branch-frontend.Dockerfile
@@ -0,0 +1,25 @@
+FROM node:20 AS build
+
+WORKDIR /build
+
+COPY package.json .
+COPY package-lock.json .
+
+RUN npm install
+
+COPY public/ public/
+COPY src/ src/
+COPY index.html .
+COPY tsconfig.json .
+COPY tsconfig.app.json .
+COPY tsconfig.node.json .
+COPY vite.config.ts .
+
+ARG BACKEND_URL
+ENV VITE_BACKEND_URL=${BACKEND_URL}
+
+RUN npm run build
+
+FROM httpd:2.4 AS runtime
+
+COPY --from=build /build/dist/ /usr/local/apache2/htdocs/