diff --git a/.github/dependabot.yml b/.github/dependabot.yaml
similarity index 91%
rename from .github/dependabot.yml
rename to .github/dependabot.yaml
index 97607e05..de791be7 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yaml
@@ -3,12 +3,12 @@ updates:
 - package-ecosystem: github-actions
   directory: /
   schedule:
-    interval: daily
-- package-ecosystem: gomod
+    interval: weekly
+- package-ecosystem: docker
   directory: /
   schedule:
     interval: daily
-- package-ecosystem: docker
+- package-ecosystem: gomod
   directory: /
   schedule:
     interval: daily
diff --git a/.github/dependency-review-config.yaml b/.github/dependency-review-config.yaml
new file mode 100644
index 00000000..df8f6535
--- /dev/null
+++ b/.github/dependency-review-config.yaml
@@ -0,0 +1,17 @@
+# https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md
+allow-licenses:
+- 'Apache-2.0'
+- 'BSD-2-Clause'
+- 'BSD-2-Clause-FreeBSD'
+- 'BSD-3-Clause'
+- 'ISC'
+- 'MIT'
+- 'PostgreSQL'
+- 'Python-2.0'
+- 'X11'
+- 'Zlib'
+
+# this action is GPL-3 but it is only used in CI
+# https://github.com/actions/dependency-review-action/issues/530#issuecomment-1638291806
+allow-dependencies-licenses: >
+  pkg:githubactions/vladopajic/go-test-coverage@bcd064e5ceef1ccec5441519eb054263b6a44787
diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml
deleted file mode 100644
index 3705f787..00000000
--- a/.github/dependency-review-config.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md
-allow_licenses:
-- 'Apache-2.0'
-- 'BSD-2-Clause'
-- 'BSD-2-Clause-FreeBSD'
-- 'BSD-3-Clause'
-- 'ISC'
-- 'MIT'
-- 'PostgreSQL'
-- 'Python-2.0'
-- 'X11'
-- 'Zlib'
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index d9c40413..8a0834c4 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -1,68 +1,54 @@
-name: Build
-on: pull_request
+name: build
+on:
+  pull_request:
+    branches:
+    - main
+permissions: {}
 jobs:
-  build:
+  build-snapshot:
+    permissions:
+      contents: read
+      packages: write
     runs-on: ubuntu-latest
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Install Go
-      uses: actions/setup-go@v5
-      with:
-        go-version: stable
-    - name: Set up environment
-      run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
-    - name: Run GoReleaser
-      uses: goreleaser/goreleaser-action@v5
-      with:
-        version: latest
-        args: build --snapshot --rm-dist
-    - name: Tar up binaries
-      # work around limitations in the upload/download artifact actions
-      # https://github.com/actions/download-artifact#limitations
-      run: tar -cvf dist.tar dist
-    - name: Upload binaries tar file
-      uses: actions/upload-artifact@v4
-      with:
-        name: dist.tar
-        path: dist.tar
-  buildimage:
-    if: ${{ !startsWith(github.head_ref, 'dependabot/') }}
     strategy:
       matrix:
         binary:
-        - ssh-portal-api
         - ssh-portal
+        - ssh-portal-api
         - ssh-token
-    needs: build
-    runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Download binaries tar file
-      uses: actions/download-artifact@v4
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+      with:
+        ref: ${{ github.event.pull_request.head.sha }}
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
-        name: dist.tar
-    - name: Untar binaries
-      run: tar -xvf dist.tar
+        go-version: stable
+    - run: echo "GOVERSION=$(go version)" >> "$GITHUB_ENV"
+    - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
+      with:
+        version: latest
+        args: build --clean --debug --single-target --snapshot
     - name: Login to GHCR
-      uses: docker/login-action@v3
+      if: github.actor != 'dependabot[bot]'
+      uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
       with:
         registry: ghcr.io
         username: ${{ github.repository_owner }}
         password: ${{ secrets.GITHUB_TOKEN }}
-    - name: Docker metadata
-      # this id is namespaced per matrix run
+    - name: Get Docker metadata
+      if: github.actor != 'dependabot[bot]'
       id: docker_metadata
-      uses: docker/metadata-action@v5
+      uses: docker/metadata-action@9dc751fe249ad99385a2583ee0d084c400eee04e # v5.4.0
       with:
-        images: ghcr.io/uselagoon/lagoon-ssh-portal/${{ matrix.binary }}
+        images: ghcr.io/${{ github.repository }}/${{ matrix.binary }}
+    - run: echo "GITHUB_REPOSITORY_NAME=$(basename ${{ github.repository }})" >> "$GITHUB_ENV"
     - name: Build and push ${{ matrix.binary }} container image
-      id: docker_build
-      uses: docker/build-push-action@v5
+      if: github.actor != 'dependabot[bot]'
+      uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
       with:
         push: true
         tags: ${{ steps.docker_metadata.outputs.tags }}
         labels: ${{ steps.docker_metadata.outputs.labels }}
-        file: deploy/${{ matrix.binary }}/Dockerfile
+        file: Dockerfile
+        build-args: BINARY=${{ matrix.binary }}
         context: dist/${{ matrix.binary }}_linux_amd64_v1
diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml
new file mode 100644
index 00000000..980097d1
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yaml
@@ -0,0 +1,32 @@
+name: codeQL
+on:
+  push:
+    branches:
+    - main
+  pull_request:
+    branches:
+    - main
+permissions: {}
+jobs:
+  analyze:
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        language:
+        - go
+    steps:
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
+      with:
+        go-version: stable
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
+      with:
+        languages: ${{ matrix.language }}
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
index 7bfa5321..9af81145 100644
--- a/.github/workflows/coverage.yaml
+++ b/.github/workflows/coverage.yaml
@@ -1,32 +1,29 @@
-name: Coverage
+name: coverage
 on:
   push:
     branches:
     - main
-
+permissions: {}
 jobs:
   coverage:
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout repository
-      uses: actions/checkout@v4
-    - name: Configure git
-      run: |
-        git config --global user.name "$GITHUB_ACTOR"
-        git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
-    - name: Set up go
-      uses: actions/setup-go@v5
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
         go-version: stable
-    - name: Install Dependencies
-      run: sudo apt-get update && sudo apt-get -u install libpcsclite-dev
     - name: Calculate coverage
       run: |
-        go test -v -covermode=count -coverprofile=coverage.out.raw -coverpkg=./... ./...
-        grep -v mock_ coverage.out.raw > coverage.out
-    - name: Convert coverage to lcov
-      uses: jandelgado/gcov2lcov-action@v1
-    - name: Coveralls
-      uses: coverallsapp/github-action@v2
+        go test -v -covermode=atomic -coverprofile=cover.out.raw -coverpkg=./... ./...
+        # remove mocks from coverage calculation
+        grep -v mock_ cover.out.raw > cover.out
+    - name: Generage coverage badge
+      uses: vladopajic/go-test-coverage@bcd064e5ceef1ccec5441519eb054263b6a44787 # v2.8.2
       with:
-        github-token: ${{ secrets.github_token }}
+        profile: cover.out
+        local-prefix: github.com/uselagoon/lagoon-ssh-portal
+        git-token: ${{ secrets.GITHUB_TOKEN }}
+        # orphan branch for storing badges
+        git-branch: badges
diff --git a/.github/workflows/dependabot-automerge.yaml b/.github/workflows/dependabot-automerge.yaml
index 8f3942a6..ac399982 100644
--- a/.github/workflows/dependabot-automerge.yaml
+++ b/.github/workflows/dependabot-automerge.yaml
@@ -1,17 +1,26 @@
 # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request
-name: Dependabot auto-merge
-on: pull_request
-
-permissions:
-  contents: write
-  pull-requests: write
-
+name: dependabot auto-merge
+on:
+  pull_request:
+    branches:
+    - main
+permissions: {}
 jobs:
-  dependabot:
+  dependabot-automerge:
+    permissions:
+      contents: write
+      pull-requests: write
     runs-on: ubuntu-latest
-    if: ${{ github.actor == 'dependabot[bot]' }}
+    if: github.actor == 'dependabot[bot]'
     steps:
-    - name: Enable auto-merge for Dependabot PRs
+    - name: Fetch dependabot metadata
+      id: metadata
+      uses: dependabot/fetch-metadata@c9c4182bf1b97f5224aee3906fd373f6b61b4526 # v1.6.0
+      with:
+        github-token: "${{ secrets.GITHUB_TOKEN }}"
+    - name: Auto-merge Dependabot PRs
+      # don't auto-merge action updates to appease OpenSSF scorecard
+      if: ${{ ! contains(steps.metadata.outputs.package-ecosystem, 'github-actions') }}
       run: gh pr merge --auto --merge "$PR_URL"
       env:
         PR_URL: ${{github.event.pull_request.html_url}}
diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml
index 15f0a9de..c1f26b3b 100644
--- a/.github/workflows/dependency-review.yaml
+++ b/.github/workflows/dependency-review.yaml
@@ -1,15 +1,16 @@
-name: 'Dependency Review'
+name: dependency review
 on:
-- pull_request
-permissions:
-  contents: read
+  pull_request:
+    branches:
+    - main
+permissions: {}
 jobs:
   dependency-review:
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     steps:
-    - name: 'Checkout Repository'
-      uses: actions/checkout@v4
-    - name: Dependency Review
-      uses: actions/dependency-review-action@v3
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - uses: actions/dependency-review-action@01bc87099ba56df1e897b6874784491ea6309bc4 # v3.1.4
       with:
-        config-file: '.github/dependency-review-config.yml'
+        config-file: .github/dependency-review-config.yaml
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 5be57d1c..5a59769c 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -1,26 +1,37 @@
-name: Lint
-on: pull_request
+name: lint
+on:
+  pull_request:
+    branches:
+    - main
+permissions: {}
 jobs:
-  golangci-lint:
-    name: lint
+  lint-go:
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Install Go
-      uses: actions/setup-go@v5
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
         go-version: stable
-    - name: golangci-lint
-      uses: golangci/golangci-lint-action@v3
+    - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
       with:
         args: --timeout=180s
-  commitlint:
+  lint-commits:
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       with:
         fetch-depth: 0
-    - name: Lint commit messages
-      uses: wagoid/commitlint-github-action@v5
+    - uses: wagoid/commitlint-github-action@0d749a1a91d4770e983a7b8f83d4a3f0e7e0874e # v5.4.4
+  lint-actions:
+    permissions:
+      contents: read
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - uses: docker://rhysd/actionlint:latest@sha256:2eb91a78b5a19140be099c7b4262d298c2567f2a9f27e10ed2a4323c5bcface8
+      with:
+        args: -color
diff --git a/.github/workflows/ossf-analysis.yaml b/.github/workflows/ossf-analysis.yaml
new file mode 100644
index 00000000..5d8bb04a
--- /dev/null
+++ b/.github/workflows/ossf-analysis.yaml
@@ -0,0 +1,31 @@
+name: OSSF scorecard
+on:
+  push:
+    branches:
+    - main
+permissions: {}
+jobs:
+  ossf-scorecard-analysis:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      # Needed if using Code scanning alerts
+      security-events: write
+      # Needed for GitHub OIDC token if publish_results is true
+      id-token: write
+    steps:
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+    - name: Run analysis
+      uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+      with:
+        results_file: results.sarif
+        results_format: sarif
+        # Publish the results for public repositories to enable scorecard badges. For more details, see
+        # https://github.com/ossf/scorecard-action#publishing-results.
+        # For private repositories, `publish_results` will automatically be set to `false`, regardless
+        # of the value entered here.
+        publish_results: true
+    - name: Upload SARIF results to code scanning
+      uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
+      with:
+        sarif_file: results.sarif
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index eb2862ce..4be6d15f 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -1,25 +1,27 @@
-name: Release
+name: release
 on:
   push:
     branches:
     - main
+permissions: {}
 jobs:
-  tag:
+  release-tag:
+    permissions:
+      # create tag
+      contents: write
     runs-on: ubuntu-latest
     outputs:
       new-tag: ${{ steps.bump-tag.outputs.new }}
       new-tag-version: ${{ steps.bump-tag.outputs.new_tag_version }}
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       with:
         fetch-depth: 0
-    - name: Configure Git
+    - name: Configure git
       run: |
         git config --global user.name "$GITHUB_ACTOR"
         git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
-    - name: Install Go
-      uses: actions/setup-go@v5
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
         go-version: stable
     - name: Install ccv
@@ -29,82 +31,43 @@ jobs:
     - name: Bump tag if necessary
       id: bump-tag
       run: |
-        if [ -z $(git tag -l $(ccv)) ]; then
-          git tag $(ccv)
+        if [ -z "$(git tag -l "$(ccv)")" ]; then
+          git tag "$(ccv)"
           git push --tags
-          echo "::set-output name=new::true"
-          echo "::set-output name=new_tag_version::$(git tag --points-at HEAD)"
+          echo "new=true" >> "$GITHUB_OUTPUT"
+          echo "new_tag_version=$(git tag --points-at HEAD)" >> "$GITHUB_OUTPUT"
         fi
-  release:
-    needs: tag
-    if: needs.tag.outputs.new-tag == 'true'
+  release-build:
+    permissions:
+      # create release
+      contents: write
+      # push docker images to regsitry
+      packages: write
+      # use OIDC token for signing
+      id-token: write
+    needs: release-tag
+    if: needs.release-tag.outputs.new-tag == 'true'
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       with:
         fetch-depth: 0
-    - name: Install Go
-      uses: actions/setup-go@v5
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
         go-version: stable
-    - name: Set up environment
-      run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
-    - name: Run GoReleaser
-      uses: goreleaser/goreleaser-action@v5
-      with:
-        version: latest
-        args: release --rm-dist
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-    - name: Tar up binaries
-      run: tar -cvf dist.tar dist
-    - name: Upload binaries tar file
-      uses: actions/upload-artifact@v4
-      with:
-        name: dist.tar
-        path: dist.tar
-  releaseimage:
-    strategy:
-      matrix:
-        binary:
-        - ssh-portal-api
-        - ssh-portal
-        - ssh-token
-    needs:
-    - tag
-    - release
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Download binaries tar file
-      uses: actions/download-artifact@v4
-      with:
-        name: dist.tar
-    - name: Untar binaries
-      run: tar -xvf dist.tar
     - name: Login to GHCR
-      uses: docker/login-action@v3
+      uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
       with:
         registry: ghcr.io
         username: ${{ github.repository_owner }}
         password: ${{ secrets.GITHUB_TOKEN }}
-    - name: Docker metadata
-      # this id is namespaced per matrix run
-      id: docker_metadata
-      uses: docker/metadata-action@v5
-      with:
-        images: ghcr.io/uselagoon/lagoon-ssh-portal/${{ matrix.binary }}
-        tags: |
-          ${{ needs.tag.outputs.new-tag-version }}
-          latest
-    - name: Build and push ${{ matrix.binary }} container image
-      id: docker_build
-      uses: docker/build-push-action@v5
+    - name: Set up environment
+      run: echo "GOVERSION=$(go version)" >> "$GITHUB_ENV"
+    - uses: sigstore/cosign-installer@9614fae9e5c5eddabb09f90a270fcb487c9f7149 # v3.3.0
+    - uses: anchore/sbom-action/download-syft@5ecf649a417b8ae17dc8383dc32d46c03f2312df # v0.15.1
+    - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
       with:
-        push: true
-        tags: ${{ steps.docker_metadata.outputs.tags }}
-        labels: ${{ steps.docker_metadata.outputs.labels }}
-        file: deploy/${{ matrix.binary }}/Dockerfile
-        context: dist/${{ matrix.binary }}_linux_amd64_v1
+        version: latest
+        args: release --clean
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/tag-to-release.yaml b/.github/workflows/tag-to-release.yaml
deleted file mode 100644
index 8b7eaa4d..00000000
--- a/.github/workflows/tag-to-release.yaml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Tag to Release
-on:
-  push:
-    tags:
-    - v*
-jobs:
-  release:
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Install Go
-      uses: actions/setup-go@v5
-      with:
-        go-version: stable
-    - name: Set up environment
-      run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
-    - name: Run GoReleaser
-      uses: goreleaser/goreleaser-action@v5
-      with:
-        version: latest
-        args: release --rm-dist
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-    - name: Tar up binaries
-      run: tar -cvf dist.tar dist
-    - name: Upload binaries tar file
-      uses: actions/upload-artifact@v4
-      with:
-        name: dist.tar
-        path: dist.tar
-  releaseimage:
-    strategy:
-      matrix:
-        binary:
-        - ssh-portal-api
-        - ssh-portal
-        - ssh-token
-    needs:
-    - tag
-    - release
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Download binaries tar file
-      uses: actions/download-artifact@v4
-      with:
-        name: dist.tar
-    - name: Untar binaries
-      run: tar -xvf dist.tar
-    - name: Login to GHCR
-      uses: docker/login-action@v3
-      with:
-        registry: ghcr.io
-        username: ${{ github.repository_owner }}
-        password: ${{ secrets.GITHUB_TOKEN }}
-    - name: Docker metadata
-      # this id is namespaced per matrix run
-      id: docker_metadata
-      uses: docker/metadata-action@v5
-      with:
-        images: ghcr.io/uselagoon/lagoon-ssh-portal/${{ matrix.binary }}
-    - name: Build and push ${{ matrix.binary }} container image
-      id: docker_build
-      uses: docker/build-push-action@v5
-      with:
-        push: true
-        tags: ${{ steps.docker_metadata.outputs.tags }}
-        labels: ${{ steps.docker_metadata.outputs.labels }}
-        file: deploy/${{ matrix.binary }}/Dockerfile
-        context: dist/${{ matrix.binary }}_linux_amd64_v1
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 4ec87137..63fa3768 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -1,14 +1,19 @@
-name: Test
-on: pull_request
+name: test
+on:
+  pull_request:
+    branches:
+    - main
+permissions: {}
 jobs:
-  go-test:
+  test-go:
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v4
-    - name: Install Go
-      uses: actions/setup-go@v5
+    - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+      with:
+        ref: ${{ github.event.pull_request.head.sha }}
+    - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
       with:
         go-version: stable
-    - name: Run Tests
-      run: go test -v ./...
+    - run: go test -v ./...
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
new file mode 100644
index 00000000..e9ca0aad
--- /dev/null
+++ b/.goreleaser.yaml
@@ -0,0 +1,140 @@
+builds:
+- &buildDefinition
+  id: ssh-portal
+  binary: ssh-portal
+  main: ./cmd/ssh-portal
+  ldflags:
+  - >
+    -s -w
+    -X "main.commit={{.Commit}}"
+    -X "main.date={{.Date}}"
+    -X "main.goVersion={{.Env.GOVERSION}}"
+    -X "main.projectName={{.ProjectName}}"
+    -X "main.version={{.Version}}"
+  env:
+  - CGO_ENABLED=0
+  goos:
+  - linux
+  - darwin
+  goarch:
+  - amd64
+  - arm64
+- <<: *buildDefinition
+  id: ssh-portal-api
+  binary: ssh-portal-api
+  main: ./cmd/ssh-portal-api
+- <<: *buildDefinition
+  id: ssh-token
+  binary: ssh-token
+  main: ./cmd/ssh-token
+
+changelog:
+  use: github-native
+
+sboms:
+- artifacts: archive
+
+signs:
+- cmd: cosign
+  signature: "${artifact}.sig"
+  certificate: "${artifact}.pem"
+  args:
+  - "sign-blob"
+  - "--output-signature=${signature}"
+  - "--output-certificate=${certificate}"
+  - "${artifact}"
+  - "--yes"
+  artifacts: checksum
+
+dockers:
+# ssh-portal
+- ids:
+  - ssh-portal
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-amd64"
+  use: buildx
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-portal"
+  - "--platform=linux/amd64"
+- ids:
+  - ssh-portal
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-arm64v8"
+  use: buildx
+  goarch: arm64
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-portal"
+  - "--platform=linux/arm64/v8"
+# ssh-portal-api
+- ids:
+  - ssh-portal-api
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-amd64"
+  use: buildx
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-portal-api"
+  - "--platform=linux/amd64"
+- ids:
+  - ssh-portal-api
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-arm64v8"
+  use: buildx
+  goarch: arm64
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-portal-api"
+  - "--platform=linux/arm64/v8"
+# ssh-token
+- ids:
+  - ssh-token
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-amd64"
+  use: buildx
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-token"
+  - "--platform=linux/amd64"
+- ids:
+  - ssh-token
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-arm64v8"
+  use: buildx
+  goarch: arm64
+  build_flag_templates:
+  - "--build-arg=BINARY=ssh-token"
+  - "--platform=linux/arm64/v8"
+
+docker_manifests:
+# ssh-portal
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-arm64v8"
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:latest"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal:{{ .Version }}-arm64v8"
+# ssh-portal-api
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-arm64v8"
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:latest"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-portal-api:{{ .Version }}-arm64v8"
+# ssh-token
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-arm64v8"
+- name_template: "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:latest"
+  image_templates:
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-amd64"
+  - "ghcr.io/{{ .Env.GITHUB_REPOSITORY }}/ssh-token:{{ .Version }}-arm64v8"
+
+docker_signs:
+- args:
+  - "sign"
+  - "${artifact}@${digest}"
+  - "--yes"
+  artifacts: all
+  output: true
diff --git a/.goreleaser.yml b/.goreleaser.yml
deleted file mode 100644
index 986118f2..00000000
--- a/.goreleaser.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-builds:
-- id: ssh-portal-api
-  dir: cmd/ssh-portal-api
-  binary: ssh-portal-api
-  ldflags:
-  - >
-    -s -w
-    -X "main.commit={{.Commit}}"
-    -X "main.date={{.Date}}"
-    -X "main.goVersion={{.Env.GOVERSION}}"
-    -X "main.projectName={{.ProjectName}}"
-    -X "main.version={{.Version}}"
-  env:
-  - CGO_ENABLED=0
-  goarch:
-  - amd64
-  goos:
-  - linux
-- id: ssh-portal
-  dir: cmd/ssh-portal
-  binary: ssh-portal
-  ldflags:
-  - >
-    -s -w
-    -X "main.commit={{.Commit}}"
-    -X "main.date={{.Date}}"
-    -X "main.goVersion={{.Env.GOVERSION}}"
-    -X "main.projectName={{.ProjectName}}"
-    -X "main.version={{.Version}}"
-  env:
-  - CGO_ENABLED=0
-  goarch:
-  - amd64
-  goos:
-  - linux
-- id: ssh-token
-  dir: cmd/ssh-token
-  binary: ssh-token
-  ldflags:
-  - >
-    -s -w
-    -X "main.commit={{.Commit}}"
-    -X "main.date={{.Date}}"
-    -X "main.goVersion={{.Env.GOVERSION}}"
-    -X "main.projectName={{.ProjectName}}"
-    -X "main.version={{.Version}}"
-  env:
-  - CGO_ENABLED=0
-  goarch:
-  - amd64
-  goos:
-  - linux
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..eef3093a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM alpine:3.19@sha256:51b67269f354137895d43f3b3d810bfacd3945438e94dc5ac55fdac340352f48
+ARG BINARY=binary-build-arg-not-defined
+ENTRYPOINT ["/${BINARY}"]
+COPY $BINARY /
diff --git a/README.md b/README.md
index 5a59b65b..e378fba9 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
 # Lagoon SSH services
 
+[![Go Reference](https://pkg.go.dev/badge/github.com/uselagoon/lagoon-ssh-portal.svg)](https://pkg.go.dev/github.com/uselagoon/lagoon-ssh-portal)
 [![Release](https://github.com/uselagoon/lagoon-ssh-portal/actions/workflows/release.yaml/badge.svg)](https://github.com/uselagoon/lagoon-ssh-portal/actions/workflows/release.yaml)
-[![Coverage](https://coveralls.io/repos/github/uselagoon/lagoon-ssh-portal/badge.svg?branch=main)](https://coveralls.io/github/uselagoon/lagoon-ssh-portal?branch=main)
+[![coverage](https://raw.githubusercontent.com/uselagoon/lagoon-ssh-portal/badges/.badges/main/coverage.svg)](https://github.com/uselagoon/lagoon-ssh-portal/actions/workflows/coverage.yaml)
 [![Go Report Card](https://goreportcard.com/badge/github.com/uselagoon/lagoon-ssh-portal)](https://goreportcard.com/report/github.com/uselagoon/lagoon-ssh-portal)
+[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/uselagoon/lagoon-ssh-portal/badge)](https://securityscorecards.dev/viewer/?uri=github.com/uselagoon/lagoon-ssh-portal)
 
 This repository contains three related SSH services for [Lagoon](https://github.com/uselagoon/lagoon).
 
diff --git a/deploy/ssh-portal-api/Dockerfile b/deploy/ssh-portal-api/Dockerfile
deleted file mode 100644
index caf632d4..00000000
--- a/deploy/ssh-portal-api/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM alpine:3.16
-ENTRYPOINT ["/ssh-portal-api"]
-COPY ssh-portal-api /
diff --git a/deploy/ssh-portal/Dockerfile b/deploy/ssh-portal/Dockerfile
deleted file mode 100644
index 4ad43196..00000000
--- a/deploy/ssh-portal/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM alpine:3.16
-ENTRYPOINT ["/ssh-portal"]
-COPY ssh-portal /
diff --git a/deploy/ssh-token/Dockerfile b/deploy/ssh-token/Dockerfile
deleted file mode 100644
index fa5d79ad..00000000
--- a/deploy/ssh-token/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM alpine:3.16
-ENTRYPOINT ["/ssh-token"]
-COPY ssh-token /