GitHub Actions / GitLab CI: deploy to branch

Why? Now your latest release is one git clone <url> -b release <local-path> away. That's what I want.

GitLab CI

image: archlinux:latest


  - pacman -Syu --noconfirm make git openssh

  stage: "build"
    - make public
    - mkdir ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - touch ~/.ssh/known_hosts
    - ssh-keyscan >> ~/.ssh/known_hosts
    - cd public
    - git init --initial-branch=release
    - git config "Built on CI"
    - git config ""
    - git add .
    - git commit -m "Deploy to Git"
    - git push --force "$CI_PROJECT_PATH" release
    - if: $CI_PIPELINE_SOURCE == "push"

Things of note

  • Substitude “make” with your build script, of course.
  • This assumes your output directory is called “public”.
  • Push access with the deploy token is still not a thing (gitlab-org/gitlab#14101), so we emulate it with SSH.

    1. Run ssh-keygen to create an SSH key locally (make sure to not overwrite your own 💥).
    2. Copy the private key
    3. Go to repository → Settings → CI/CD → Variables and add a new Variable, SSH_PRIVATE_KEY, then paste the private key in it.

      • The name is not special, just has to match what's in .gitlab-ci.yml
      • For some reason I still get the file contents when I use type = File, so I just don't bother.
    4. Copy the public key
    5. Go to repository → Settings → Repository → Deploy Keys, then paste it there

      • Grant write permissions as that's the thing we're using it for
    6. Done! Now pushing to the repository should result in the build also being pushed to the release branch.
  • The private key needs to have the right Unix permissions.
  • SSH rejects new hosts by default when run noninteractively, so we need to add an ssh-keyscan in there to tell SSH we trust Figuring out why this does not increase the amount of trust we put in is exercise for the reader. (Hint: we already trust

GitHub Actions

name: "release"
      - main


    runs-on: ubuntu-latest
      # We rely on actions/checkout
      - uses: actions/checkout@v2
      - name: "Build"
        run: |
      - name: "Push"
        run: |
          git config "Built on CI"
          git config ""
          cd dist
          mv ../.git .
          git checkout --orphan release
          git add .
          git commit -m "Deploy to Git"
          git push --force origin release          

Things of note

  • Substitude “make” with your build script.
  • This assumes your output directory is called “dist”.
  • actions/checkout already sets things up so that pushing will just work, but we still have to set up the directory structure of the new orphan branch. The way I chose to do this is to just move .git, which is functionally the same as first deleting everything except .git and dist, then moving everything in dist to project root, then deleting dist itself.