Skip to content

Bump ruby/setup-ruby from 1.310.0 to 1.313.0 #201

Bump ruby/setup-ruby from 1.310.0 to 1.313.0

Bump ruby/setup-ruby from 1.310.0 to 1.313.0 #201

Workflow file for this run

name: CI/CD
on:
pull_request:
branches: [main]
push:
branches: [main]
# AWS auth uses GitHub OIDC. The role ARN is a non-sensitive repo variable
# (Settings → Secrets and variables → Actions → Variables) — Terraform creates
# the role and exposes its ARN via the `github_actions_role_arn` output.
env:
AWS_REGION: eu-west-2
AWS_OIDC_ROLE: ${{ vars.AWS_OIDC_ROLE_ARN }}
# Mirrors local.secret_prefix in infrastructure/terraform/secrets.tf (leading
# slash is the Parameter Store convention).
AWS_SECRET_PREFIX: /cpcwood-k8s/home-server/production
# Always re-evaluate the flake rather than trust a cached drv path — guards
# against the dangling-drv error (NixOS/nix#4236) if a restored store is stale.
NIX_CONFIG: "eval-cache = false"
permissions:
contents: read
# Actions are pinned to a full-length commit SHA (tag in a trailing comment)
# because the SHA is the only immutable reference — a tag can be re-pointed by
# the action author. Dependabot (.github/dependabot.yml) bumps the SHA and the
# comment together when a new release ships.
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_USER: cpcwood
POSTGRES_PASSWORD: test
POSTGRES_DB: home_server_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
BUNDLE_JOBS: "3"
BUNDLE_RETRY: "3"
PGHOST: localhost
PGUSER: cpcwood
PGPASSWORD: test
DB_NAME_TEST: home_server_test
RAILS_ENV: test
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1
with:
ruby-version: 3.2.3
bundler-cache: true
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '18'
cache: 'yarn'
- run: yarn install --frozen-lockfile
- uses: browser-actions/setup-chrome@c785b87e244131f27c9f19c1a33e2ead956ab7ce # v1
# mini_magick (config.active_storage.variant_processor) shells out to it.
- run: sudo apt-get update && sudo apt-get install -y imagemagick
- run: bundle exec rails db:schema:load --trace
- run: bundle exec rspec
- run: bundle exec rubocop
- run: yarn test
- run: yarn lint
- name: Upload coverage to Coveralls
if: ${{ !cancelled() }}
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
with:
# github-token here is the Coveralls repo token, not GITHUB_TOKEN — the
# action forwards it to the reporter as COVERALLS_REPO_TOKEN. Empty on
# Dependabot PRs (Actions secrets are withheld there); fail-on-error
# keeps that from breaking the build.
github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}
file: coverage/.resultset.json
format: simplecov
fail-on-error: false
allow-empty: true
helm-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
determinate: false
# cache-nix-action tars and restores /nix/store verbatim; auto-optimise
# hardlinks (/nix/store/.links) hit the tar's skip-list and fail to
# relink on restore → partial store → missing-drv in nix-develop.
extra-conf: |
auto-optimise-store = false
# Exact-key restore; saves (uploads) only on a miss — i.e. only when the
# flake changes. Unchanged-flake runs restore and skip the upload entirely.
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
with:
arguments: ./infrastructure
- name: Helm dependency build
working-directory: ./infrastructure/helm
run: helm dependency build
- name: Helm lint
run: helm lint ./infrastructure/helm
- name: Helm template (umbrella renders cleanly)
run: helm template home-server ./infrastructure/helm --namespace home-server-production > /tmp/rendered.yaml
- name: Assert no plaintext secrets leak into rendered manifests
run: |
if grep -E -i '(SECRET_KEY_BASE|TWILIO_AUTH_TOKEN|AWS_SECRET_ACCESS_KEY|POSTGRES_PASSWORD)\s*:\s*["'\''][^"'\'']+' /tmp/rendered.yaml; then
echo "::error::Plaintext secret detected in rendered chart"
exit 1
fi
# pre-commit covers terraform fmt/validate/tflint/trivy/checkov + shellcheck +
# the generic file hooks, using the same config and tool versions developers
# run locally (.pre-commit-config.yaml + infrastructure/flake.nix).
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
determinate: false
# cache-nix-action tars and restores /nix/store verbatim; auto-optimise
# hardlinks (/nix/store/.links) hit the tar's skip-list and fail to
# relink on restore → partial store → missing-drv in nix-develop.
extra-conf: |
auto-optimise-store = false
# Exact-key restore; saves (uploads) only on a miss — i.e. only when the
# flake changes. Unchanged-flake runs restore and skip the upload entirely.
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
with:
arguments: ./infrastructure
# Cache tflint's AWS ruleset so `tflint --init` makes zero GitHub API calls
# on a hit; it only re-fetches when the pinned version in .tflint.hcl changes.
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.tflint.d/plugins
key: tflint-${{ runner.os }}-${{ hashFiles('infrastructure/.tflint.hcl') }}
- run: pre-commit run --all-files --show-diff-on-failure
env:
# Fallback for a cache miss (version bump): authenticate tflint's plugin
# download so it isn't capped by the anonymous 60/hr per-IP limit.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run `terraform plan` against real state and post the diff as a PR comment.
# Gated to same-repo PRs because the OIDC role can read SSM SecureString
# values; a fork PR could craft a `nonsensitive()` output to exfiltrate.
terraform-plan:
needs: [lint]
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
defaults:
run:
working-directory: ./infrastructure/terraform
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
determinate: false
# cache-nix-action tars and restores /nix/store verbatim; auto-optimise
# hardlinks (/nix/store/.links) hit the tar's skip-list and fail to
# relink on restore → partial store → missing-drv in nix-develop.
extra-conf: |
auto-optimise-store = false
# Exact-key restore; saves (uploads) only on a miss — i.e. only when the
# flake changes. Unchanged-flake runs restore and skip the upload entirely.
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
with:
arguments: ./infrastructure
- uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE }}
aws-region: ${{ env.AWS_REGION }}
# The kubernetes provider (ci.tf) needs the cluster reachable at plan time.
- name: Configure kubeconfig
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
run: |
mkdir -p ~/.kube
printf '%s' "$KUBE_CONFIG_DATA" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: terraform init
run: terraform init -input=false
- id: plan
run: |
set +e
terraform plan -no-color -input=false -detailed-exitcode -out=tfplan > plan.txt 2>&1
ec=$?
cat plan.txt
if [[ $ec -eq 1 ]]; then
echo "::error::terraform plan failed"
exit 1
fi
echo "exitcode=$ec" >> "$GITHUB_OUTPUT"
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const fs = require('fs');
const path = require('path');
const marker = '<!-- terraform-plan -->';
const exitcode = '${{ steps.plan.outputs.exitcode }}';
const planPath = path.join(
process.env.GITHUB_WORKSPACE,
'infrastructure', 'terraform', 'plan.txt'
);
let plan = fs.readFileSync(planPath, 'utf8');
// GitHub comment hard limit is 65536 chars. Keep headroom.
if (plan.length > 60000) {
plan = plan.substring(0, 60000) + '\n... (truncated)';
}
const header = exitcode === '0'
? 'No changes. Infrastructure matches configuration.'
: 'Plan has pending changes — review before merging.';
const body = `${marker}\n### Terraform plan\n\n${header}\n\n<details><summary>Plan output</summary>\n\n\`\`\`hcl\n${plan}\n\`\`\`\n\n</details>`;
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existing = comments.find(c => c.body.startsWith(marker));
if (existing) {
await github.rest.issues.updateComment({
comment_id: existing.id,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
}
# The single required status check for branch protection. Aggregates every PR
# job so the rule needn't enumerate each — matrix builds have fragile,
# path-derived names and GitHub required checks don't support wildcards.
# always() so it runs (and reports) even when a needed job fails; skipped
# needs (e.g. fork PRs) don't count as failures.
ci-gate:
needs: [test, helm-validate, lint, terraform-plan, build-base, build-app]
if: always() && github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "Not all required jobs passed:"
echo '${{ toJSON(needs) }}'
exit 1
- run: echo "All required jobs passed."
automerge:
needs: [test, helm-validate, lint]
if: github.event_name == 'pull_request' && github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
# PAT (a Dependabot secret), not GITHUB_TOKEN: an auto-merge enabled
# via GITHUB_TOKEN does not trigger the push→deploy workflow (Actions
# anti-recursion), so a bump would merge but never deploy.
GH_TOKEN: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
terraform:
needs: [test, helm-validate, lint]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: ./infrastructure/terraform
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
determinate: false
# cache-nix-action tars and restores /nix/store verbatim; auto-optimise
# hardlinks (/nix/store/.links) hit the tar's skip-list and fail to
# relink on restore → partial store → missing-drv in nix-develop.
extra-conf: |
auto-optimise-store = false
# Exact-key restore; saves (uploads) only on a miss — i.e. only when the
# flake changes. Unchanged-flake runs restore and skip the upload entirely.
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
with:
arguments: ./infrastructure
- uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE }}
aws-region: ${{ env.AWS_REGION }}
# The kubernetes provider (ci.tf) needs the cluster reachable at apply time.
- name: Configure kubeconfig
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
run: |
mkdir -p ~/.kube
printf '%s' "$KUBE_CONFIG_DATA" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: terraform init
run: terraform init -input=false
- run: terraform plan -out=tfplan
- run: terraform apply -auto-approve tfplan
# Images build in two phases so app/worker are assembled from the base built
# THIS run, not a stale registry copy. Phase 1 (build-base) builds + pushes
# base and worker-dependencies to GHCR by commit SHA; phase 2 (build-app)
# remaps the Dockerfiles' `COPY --from=cpcwood/home-server-*` to those fresh
# images via build-contexts. Same-repo PRs run both (base is pushed by SHA so
# phase 2 can pull it; app/worker are built but not published). Fork PRs are
# excluded — no OIDC for the base build secrets.
build-base:
needs: [test, helm-validate, lint]
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
packages: write
strategy:
matrix:
image:
- name: base
dockerfile: ./.docker/dockerfiles/base.Dockerfile
needs_build_secrets: true
- name: worker-dependencies
dockerfile: ./.docker/dockerfiles/worker-dependencies.Dockerfile
needs_build_secrets: false
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
if: matrix.image.needs_build_secrets
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE }}
aws-region: ${{ env.AWS_REGION }}
# Only `base` fetches build-time secrets, so only it pays the nix-install cost.
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
if: matrix.image.needs_build_secrets
with:
determinate: false
extra-conf: |
auto-optimise-store = false
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
if: matrix.image.needs_build_secrets
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
if: matrix.image.needs_build_secrets
with:
arguments: ./infrastructure
- id: build_secrets
if: matrix.image.needs_build_secrets
run: |
set -euo pipefail
json=$(aws ssm get-parameter \
--name "${AWS_SECRET_PREFIX}/build" \
--with-decryption \
--query Parameter.Value \
--output text)
mml=$(jq -r .MAX_MIND_LICENSE <<<"$json")
grck=$(jq -r .GRECAPTCHA_SITE_KEY <<<"$json")
echo "::add-mask::$mml"
echo "::add-mask::$grck"
echo "BUILD_MAX_MIND_LICENSE=$mml" >> "$GITHUB_ENV"
echo "BUILD_GRECAPTCHA_SITE_KEY=$grck" >> "$GITHUB_ENV"
- id: args
run: |
if [[ "${{ matrix.image.needs_build_secrets }}" == "true" ]]; then
cat >>"$GITHUB_OUTPUT" <<EOF
build_args<<ARGS_EOF
MAX_MIND_LICENSE=$BUILD_MAX_MIND_LICENSE
grecaptcha_site_key=$BUILD_GRECAPTCHA_SITE_KEY
ARGS_EOF
EOF
else
echo "build_args=" >>"$GITHUB_OUTPUT"
fi
# Pull buildkit + the Dockerfiles' base images through Google's public
# docker.io mirror to dodge Docker Hub anonymous rate limits / timeouts.
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
driver-opts: image=mirror.gcr.io/moby/buildkit:buildx-stable-1
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
# Always log in: base is pushed by SHA on PRs too, so phase 2 can pull it.
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: ${{ matrix.image.dockerfile }}
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/home-server-${{ matrix.image.name }}:${{ github.sha }}
${{ github.event_name == 'push' && format('ghcr.io/{0}/home-server-{1}:latest', github.repository_owner, matrix.image.name) || '' }}
build-args: ${{ steps.args.outputs.build_args }}
cache-from: type=gha,scope=${{ matrix.image.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}
build-app:
needs: [build-base]
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
image:
- name: app
dockerfile: ./.docker/dockerfiles/Dockerfile
- name: worker
dockerfile: ./.docker/dockerfiles/worker.Dockerfile
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
driver-opts: image=mirror.gcr.io/moby/buildkit:buildx-stable-1
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io"]
# Log in to pull the freshly-pushed (private) base/worker-deps, and to push
# app/worker on main.
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Remap each Dockerfile's `COPY --from=cpcwood/home-server-*` to the image
# built this run, so app/worker carry current code rather than a stale copy.
- id: ctx
run: |
repo="ghcr.io/${{ github.repository_owner }}"
sha="${{ github.sha }}"
{
echo "contexts<<CTX_EOF"
echo "cpcwood/home-server-base=docker-image://${repo}/home-server-base:${sha}"
if [[ "${{ matrix.image.name }}" == "worker" ]]; then
echo "cpcwood/home-server-worker-dependencies=docker-image://${repo}/home-server-worker-dependencies:${sha}"
fi
echo "CTX_EOF"
} >> "$GITHUB_OUTPUT"
- uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: ${{ matrix.image.dockerfile }}
build-contexts: ${{ steps.ctx.outputs.contexts }}
# Push only on main; PR runs build-validate without publishing.
push: ${{ github.event_name == 'push' }}
tags: |
ghcr.io/${{ github.repository_owner }}/home-server-${{ matrix.image.name }}:${{ github.sha }}
${{ github.event_name == 'push' && format('ghcr.io/{0}/home-server-{1}:latest', github.repository_owner, matrix.image.name) || '' }}
cache-from: type=gha,scope=${{ matrix.image.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}
deploy:
needs: [terraform, build-app]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
determinate: false
# cache-nix-action tars and restores /nix/store verbatim; auto-optimise
# hardlinks (/nix/store/.links) hit the tar's skip-list and fail to
# relink on restore → partial store → missing-drv in nix-develop.
extra-conf: |
auto-optimise-store = false
# Exact-key restore; saves (uploads) only on a miss — i.e. only when the
# flake changes. Unchanged-flake runs restore and skip the upload entirely.
- uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7
with:
primary-key: nix6-${{ runner.os }}-${{ hashFiles('infrastructure/flake.nix', 'infrastructure/flake.lock') }}
purge: true
purge-prefixes: nix6-${{ runner.os }}-
purge-created: 0
purge-last-accessed: 604800
purge-primary-key: never
- uses: nicknovitski/nix-develop@9be7cfb4b10451d3390a75dc18ad0465bed4932a # v1
with:
arguments: ./infrastructure
- name: Configure kubeconfig
env:
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }}
run: |
mkdir -p ~/.kube
printf '%s' "$KUBE_CONFIG_DATA" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- run: helm dependency build ./infrastructure/helm
- name: Helm upgrade
run: |
helm upgrade --install home-server ./infrastructure/helm \
--namespace home-server-production \
--atomic --timeout 5m \
--set app.image.tag=${{ github.sha }} \
--set worker.image.tag=${{ github.sha }}