DDEV is a local development tool that makes it easy to run web projects like Drupal, WordPress, or Laravel using Docker.
It standardizes environments, so every developer on a project uses the same PHP, MySQL, and server stack—no “works on my machine” issues.
It uses containerization via Docker, creating isolated, reproducible environments that mirror production without polluting your system.
GitHub Actions is a CI/CD tool built directly into GitHub that automates workflows like testing, building, and deploying code.
It runs workflows in response to events like pushes, pull requests, or manual triggers, making automation seamless and integrated.
Workflows are defined using YAML and can run in isolated containers or virtual machines, allowing flexible and repeatable automation steps.
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
# PRESENTING CODE
| Name | Description | Example |
|---|---|---|
| github | Info about the workflow run (event, actor, repo, branch, etc.) | ${{ github.actor }} |
| runner | Info about the virtual machine (OS, architecture, etc.) | ${{ runner.os }} |
| job | Info about the current job (status, outputs, etc.) | ${{ job.status }} |
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
# PRESENTING CODE
| Name | Description |
|---|---|
| name | Persistent name that shows up on github actions |
| run-name | Dynamic name |
| on | What should cause this action to execute? |
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event by ${{ github.actor }}"
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
# PRESENTING CODE
| Name | Description |
|---|---|
| jobs | What the workflow will do |
| Explore-GitHub-Actions | Dynamic name of the job |
| runs-on | What environment will this run on |
| steps | Execution steps of the job |
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event by ${{ github.actor }}"
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
# PRESENTING CODE
| Name | Description |
|---|---|
| run | Runs a command |
| name | (Optional) Gives the step a readable label |
| uses | Calls a reusable action |
Let's see it in action.
Environment variables store reusable values like API keys, config options, or dynamic data that can be accessed across steps in a job.
They can be defined globally, per job, or per step to control scope and avoid repetition.
Use them to simplify scripts, avoid hardcoding values, and make workflows easier to maintain and update.
name: Env Variable Example
on: [push]
env: # 🌍 Global environment variable (applies to all jobs and steps)
GLOBAL_MESSAGE: "Hello from global level"
jobs:
example-job:
runs-on: ubuntu-latest
env: # 💼 Job-level environment variable (applies to all steps in this job)
JOB_MESSAGE: "Hello from job level"
steps:
- name: Step 1 – Use global and job env
run: |
echo "$GLOBAL_MESSAGE"
echo "$JOB_MESSAGE"
- name: Step 2 – Override with step-level env
env: # 🔹 Step-level environment variable (applies only to this step)
STEP_MESSAGE: "Hello from step level"
run: |
echo "$GLOBAL_MESSAGE"
echo "$JOB_MESSAGE"
echo "$STEP_MESSAGE"
# PRESENTING CODE
Use toJson(...) to inspect context variables, like github, env, or secrets, and pipe them through tools like jq for readable output.
Print all environment variables with shell commands like printenv | sort (Linux/macOS) or Get-ChildItem Env: (Windows) to troubleshoot config and path issues.
Re-run workflows with debug logging enabled by selecting “Re-run jobs > Re-run with debug logging” in the Actions tab — this enables runner.debug and prints more verbose internal logs.
Special outputs - ::warning::, ::debug::, ::error::
name: Context Debug Example
on: [push]
jobs:
debug-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Full Debug Info
run: |
echo "🔍 GitHub Context:"
echo '${{ toJson(github) }}' | jq
echo "🧪 Environment Variables:"
printenv | sort
echo "Repository URL: ${{ github.repositoryUrl }}"
echo "Committer Name: ${{ github.event.head_commit.committer.name }}"
echo "::warning::This is a warning"
echo "::error::This is an error message"
# PRESENTING CODE
name: Context Debug Example
on: [push]
jobs:
debug-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Full Debug Info
if: runner.debug == '1'
run: |
echo "🔍 GitHub Context:"
echo '${{ toJson(github) }}' | jq
echo "🧪 Environment Variables:"
printenv | sort
echo "💻 Runner Context:"
echo '${{ toJson(runner) }}' | jq
echo "Repository URL: ${{ github.repositoryUrl }}"
echo "Committer Name: ${{ github.event.head_commit.committer.name }}"
echo "::warning::This is a warning"
echo "::error::This is an error message"
# PRESENTING CODE
Defined in repo or org settings under Settings → Secrets and variables → Actions → Secrets.
Accessed in workflows using the secrets context
${{ secrets.MY_SECRET_KEY }}
Also defined in repo or org settings under Settings → Secrets and variables → Actions → Variables.
Accessed using the vars context:
${{ vars.ENVIRONMENT_NAME }}
name: Context Variables and Secrets Demo
on: [push]
jobs:
debug-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Github context
run: |
echo "Acquia API Key: ${{ secrets.MY_SECRET }}"
echo "My Var: ${{ vars.MY_VAR }}"
# PRESENTING CODE
Pull Request
Developer builds a new feature or bug fix and performs a pull request to a release branch.
Build
DDEV environment is instantiated, Drupal site is built with composer package manager.
Launch
ACLI executes commands to push code and build an One Demand environment for smoke testing
- name: Setup SSH keys
run: |
mkdir -p .ddev/homeadditions/.ssh
echo "${{ secrets.ACQUIA_SSH_KEY }}" > .ddev/homeadditions/.ssh/id_rsa
chmod 700 .ddev/homeadditions/.ssh
chmod 600 .ddev/homeadditions/.ssh/id_rsa
- name: Setup DDEV
uses: ddev/github-action-setup-ddev@v1
- name: Build & Push
run: |
ddev exec acli --no-interaction self-update --compatible
ddev exec acli --no-interaction auth:login --key="${{ secrets.ACQUIA_API_KEY }}" --secret="${{ secrets.ACQUIA_API_SECRET }}"
ddev exec acli --no-interaction push:artifact ${ACQUIA_APP}.dev --destination-git-branch="${BUILD_PREFIX}${{ env.TARGET_BRANCH_NAME }}" -vvv
# PRESENTING CODE
Lets see a full example
New Workflows GUI: GitHub’s revamped interface for creating and managing workflows visually, making it easier to understand workflow relationships and execution flow without diving into raw YAML.
Caches: Store dependencies or build outputs between workflow runs to speed up jobs and reduce redundant processing (e.g., npm packages, composer dependencies).
Attestations: Provide verifiable metadata about workflow runs or artifacts, used for software supply chain security and compliance (e.g., proving a build came from a specific GitHub Action).
Runners: The virtual machines that execute your workflows; GitHub offers hosted runners (Linux, Windows, macOS) or you can set up self-hosted runners for more control.
Usage Metrics: Track how much compute time your workflows are using, broken down by job, repository, or organization — helpful for cost management and optimization.
Performance Metrics: View workflow performance over time, such as job durations, success rates, and trends — used to identify bottlenecks or unstable steps.