Secrets, permissions and deployments
Use secrets and variables safely, give GITHUB_TOKEN the least permission, gate deployments with environments, and publish with GitHub Pages.
- Store and use secrets and variables in workflows without leaking them
- Limit what the GITHUB_TOKEN can do, and prefer OIDC to long-lived cloud keys
- Deploy through protected environments, schedule jobs, and publish a site with GitHub Pages
Deploying Dungeon Dash’s web demo needs credentials, and credentials must never live in the repository. Instead:
- Secrets (Settings → Secrets and variables → Actions) are encrypted values available to workflows as
${{ secrets.NAME }}. They’re masked in logs - printed as***. - Variables (
${{ vars.NAME }}) hold non-secret configuration, like a server name. - Secrets can belong to a repository, an organization (shared by many repos), or an environment.
1name: Deploy demo
2on:
3 push:
4 branches: [main]
5
6permissions:
7 contents: read # least privilege for the GITHUB_TOKEN
8
9jobs:
10 deploy:
11 runs-on: ubuntu-latest
12 environment: production # protected: needs approval, has its own secrets
13 steps:
14 - uses: actions/checkout@v4
15 - run: ./scripts/deploy.sh "${{ vars.DEMO_HOST }}"
16 env:
17 DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Points worth noticing:
- The secret reaches the script as an environment variable, not pasted into the command line where it could show up in process listings or logs.
permissions:limits the automaticGITHUB_TOKENevery run gets. Its default can be broad; granting onlycontents: readmeans a compromised step can’t push code or edit releases. Add what a job really needs:pull-requests: writeto comment on PRs,packages: writeto publish packages.environment: productionties the job to an environment, which can require a reviewer’s approval before it runs, restrict which branches can deploy, and hold its own secrets.- Secrets aren’t passed to workflows triggered by pull requests from forks - otherwise anyone could open a PR that prints your secrets.
1secrets = {"DEPLOY_TOKEN": "ghp_4f9Xk2", "DB_PASSWORD": "dragon$fire"}
2log = """Deploying to demo.dungeondash.dev
3Using token ghp_4f9Xk2 for auth
4Connecting with password dragon$fire
5Done"""
6for value in sorted(secrets.values(), key=len, reverse=True):
7 log = log.replace(value, "***")
8print(log)Deploying to demo.dungeondash.dev Using token *** for auth Connecting with password *** Done
Schedules and GitHub Pages
Scheduled workflows use cron syntax - five fields: minute, hour, day of month, month, day of week (0 = Sunday) - always in UTC. 0 3 * * 1 is 03:00 every Monday; */15 * * * * is every 15 minutes (the shortest interval GitHub allows is 5 minutes, and scheduled runs can start a little late).
GitHub Pages hosts static websites for free, straight from a repository: project docs, a portfolio, or Dungeon Dash’s web demo. Build the site in a workflow with actions/upload-pages-artifact, deploy with actions/deploy-pages, and it appears at https://OWNER.github.io/REPO/.
Key takeaways
Keep credentials in Actions secrets (masked in logs) and plain configuration in variables.
Set
permissions:so the GITHUB_TOKEN has only what each job needs; prefer OIDC over cloud keys.Protected environments add approvals, branch rules and their own secrets to deployments.
Cron schedules run in UTC; GitHub Pages hosts static sites built by a workflow.
Lesson quiz
7 questions · pass with 5 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: automate GitHub chores with Python
Real GitHub work involves lots of small automation: matching CODEOWNERS, expanding build matrices, bumping versions, reading the API. Write those helpers in Python and run them against sample inputs - locally in your browser, with no GitHub account needed.
Mask the secrets
The input has NAME=value secret lines, a line ---, then a build log. Print the log with every occurrence of each secret value replaced by *** (replace longer secrets first, so a secret containing another is masked whole). Then print masked N occurrences and, if any secret appears base64-encoded in the log, warning: NAME appears base64-encoded.
- Deploy log
- Nested secrets
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Does the schedule fire?
Each input line is a cron expression in quotes, then a UTC date and time: "0 3 * * 1" 2026-10-05 03:00. Print fires or doesn’t fire for each.
Support, in each of the five fields (minute, hour, day of month, month, day of week with 0 = Sunday): *, numbers, comma lists 1,15, ranges 1-5, and steps */15 or 0-30/10. As in standard cron, when both day-of-month and day-of-week are restricted (not *), a match on either is enough.
- Schedules
- Day OR weekday
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…