Skip to main content

GitHub Actions

Fetch secrets from Arcan and inject them as environment variables or write them to a .env file in your GitHub Actions workflows.

Usage

Fetch all secrets

steps:
- uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: my-app
env: prod

- run: echo "Database is configured"
# DATABASE_URL, API_KEY, etc. are now available as env vars

Fetch specific secrets

steps:
- uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: my-app
keys: DATABASE_URL,API_KEY,STRIPE_SECRET

- run: npm test

Write to .env file

steps:
- uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: my-app
export_file: .env
export_env: 'false'

- run: docker compose up -d

Full workflow example

name: Deploy
on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Load secrets
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: my-app
env: prod
keys: DATABASE_URL,REDIS_URL,API_KEY

- name: Deploy
run: ./deploy.sh

Inputs

InputRequiredDefaultDescription
arcan_urlYes--Arcan server URL
arcan_tokenYes--Arcan API token (arc_xxx or JWT)
realmYes--Realm slug
envNodevEnvironment (dev, staging, prod)
keysNo--Comma-separated keys. Fetches all if empty.
export_envNotrueExport secrets as environment variables
export_fileNo--Write secrets to a .env file at this path
insecureNofalseAllow self-signed TLS certificates (dev only)

Security

  • All secret values are automatically masked using ::add-mask:: so they never appear in workflow logs
  • Store your ARCAN_TOKEN as a GitHub Actions secret, never hardcode it
  • Use valid TLS certificates in production (not self-signed)
  • Set insecure: 'true' only for development servers with self-signed certificates
  • Use read-only tokens (arcan token create --scopes read) for applications that only need to read secrets
  • Enable audit logging on the Arcan server to track all secret access

Complete Workflow: Node.js Deployment with Docker

This workflow fetches secrets, runs tests, builds a Docker image with injected secrets, and deploys to staging and production:

name: Build and Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options:
- staging
- prod

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- run: npm ci

- name: Load test secrets
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: myapp
env: dev
keys: DATABASE_URL,REDIS_URL,STRIPE_TEST_KEY

- run: npm test

build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=

- name: Build and push image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4

- name: Load staging secrets
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: myapp
env: staging
keys: DATABASE_URL,REDIS_URL,STRIPE_SECRET,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY

- name: Deploy to staging
run: |
echo "Deploying ${{ needs.build.outputs.image_tag }} to staging..."
./scripts/deploy.sh staging ${{ needs.build.outputs.image_tag }}

- name: Smoke test
run: |
sleep 10
curl --fail https://staging.myapp.com/health || exit 1

deploy-prod:
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4

- name: Load production secrets
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: myapp
env: prod
keys: DATABASE_URL,REDIS_URL,STRIPE_SECRET,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,SENTRY_DSN

- name: Deploy to production
run: |
echo "Deploying ${{ needs.build.outputs.image_tag }} to production..."
./scripts/deploy.sh prod ${{ needs.build.outputs.image_tag }}

- name: Verify deployment
run: |
sleep 15
curl --fail https://myapp.com/health || exit 1
tip

Use GitHub's environment protection rules to require manual approval before production deployments. The staging environment gets its own set of Arcan secrets, and the production environment requires a reviewer.

Complete Workflow: Node.js Test, Build, and Deploy

A focused workflow that runs tests with test secrets, builds, and deploys a Node.js app with production secrets injected at deploy time:

name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- run: npm ci

- name: Load test secrets from Arcan
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: myapp
env: dev
keys: DATABASE_URL,REDIS_URL,STRIPE_TEST_KEY

- name: Run unit tests
run: npm test

- name: Run integration tests
run: npm run test:integration

build:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- run: npm ci

- name: Build application
run: npm run build

- name: Determine version
id: version
run: echo "version=$(node -p 'require(\"./package.json\").version')-${{ github.sha }}" >> "$GITHUB_OUTPUT"

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ steps.version.outputs.version }}
path: dist/

deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist-${{ needs.build.outputs.version }}
path: dist/

- name: Load production secrets from Arcan
uses: GetArcan/arcan-action@v1
with:
arcan_url: ${{ secrets.ARCAN_URL }}
arcan_token: ${{ secrets.ARCAN_TOKEN }}
realm: myapp
env: prod
keys: DATABASE_URL,REDIS_URL,STRIPE_SECRET,SENTRY_DSN,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY

- name: Deploy to production
run: |
npm install -g @myapp/deployer
deploy --version ${{ needs.build.outputs.version }} --target production

- name: Verify deployment
run: |
sleep 10
curl --fail --retry 3 --retry-delay 5 https://myapp.com/health || exit 1
echo "Deployment verified successfully"

Key points about this workflow:

  • Test job uses env: dev secrets so integration tests hit a test database
  • Build job is secret-free -- it produces a portable artifact
  • Deploy job uses env: prod secrets only at deploy time, with GitHub environment protection requiring manual approval
  • All secret values are automatically masked in logs via ::add-mask::

Requirements

  • curl and jq (pre-installed on GitHub-hosted runners)
  • Network access to your Arcan server from the runner