Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/workflows/hosted-runner-docker-architecture.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
name: Hosted runner Docker architecture proof

on:
pull_request:
branches:
- develop
- main
paths:
- .github/workflows/hosted-runner-docker-architecture.yml
workflow_dispatch:
Comment thread
joecklau marked this conversation as resolved.

permissions:
contents: read

concurrency:
group: hosted-docker-architecture-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
capability:
name: Capability (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
- ubuntu-24.04-arm
- windows-latest
- windows-11-arm
- macos-latest
- macos-15-intel

steps:
- name: Record host and Docker capability
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
$summary = [System.Collections.Generic.List[string]]::new()
$summary.Add("## $env:RUNNER_OS/$env:RUNNER_ARCH on ``${{ matrix.runner }}``")
$summary.Add('')
$summary.Add("- Runner OS: ``$env:RUNNER_OS``")
$summary.Add("- Runner architecture: ``$env:RUNNER_ARCH``")

$dockerCommand = Get-Command docker -ErrorAction SilentlyContinue
if (-not $dockerCommand) {
$summary.Add('- Docker CLI: not installed')
$summary.Add('- Linux container test: unavailable')
} else {
$summary.Add("- Docker CLI: ``$($dockerCommand.Source)``")
$versionOutput = (& docker version 2>&1 | Out-String).Trim()
$versionStatus = $LASTEXITCODE
$summary.Add("- Docker daemon reachable: ``$($versionStatus -eq 0)``")

if ($versionStatus -eq 0) {
$serverOS = (& docker info --format '{{.OSType}}' 2>&1 | Out-String).Trim()
$serverArchitecture = (& docker info --format '{{.Architecture}}' 2>&1 | Out-String).Trim()
$summary.Add("- Docker server: ``$serverOS/$serverArchitecture``")
if ($serverOS -eq 'linux') {
$summary.Add('- Linux container test: available')
} else {
$summary.Add('- Linux container test: unavailable because the reachable daemon is not a Linux daemon')
}
} else {
$summary.Add('- Linux container test: unavailable because no Docker daemon is reachable')
$summary.Add('')
$summary.Add('<details><summary>docker version diagnostic</summary>')
$summary.Add('')
$summary.Add('```text')
$summary.Add($versionOutput)
$summary.Add('```')
$summary.Add('</details>')
}
}

$summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
$summary | ForEach-Object { Write-Host $_ }

linux-cross-architecture:
name: Linux ${{ matrix.native }} runs ${{ matrix.foreign }} with explicit QEMU
runs-on: ${{ matrix.runner }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
native: amd64
native_platform: linux/amd64
native_output: x86_64
foreign: arm64
foreign_platform: linux/arm64
foreign_output: aarch64
- runner: ubuntu-24.04-arm
native: arm64
native_platform: linux/arm64
native_output: aarch64
foreign: amd64
foreign_platform: linux/amd64
foreign_output: x86_64

steps:
- name: Verify native container execution
shell: bash
run: |
set -euo pipefail
docker info --format 'Docker server: {{.OSType}}/{{.Architecture}}'
native_architecture="$(docker run --rm --platform '${{ matrix.native_platform }}' alpine:3.22 uname -m)"
echo "Native container reported: ${native_architecture}"
[[ "${native_architecture}" == '${{ matrix.native_output }}' ]]

- name: Observe foreign-architecture behavior before explicit QEMU setup
id: baseline
shell: bash
run: |
set -euo pipefail
set +e
baseline_output="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m 2>&1)"
baseline_status=$?
set -e

printf '%s\n' "${baseline_output}"
{
echo '## ${{ matrix.foreign }} container before explicit QEMU setup'
echo
echo "- Exit status: \`${baseline_status}\`"
echo
echo '```text'
printf '%s\n' "${baseline_output}"
echo '```'
} >>"${GITHUB_STEP_SUMMARY}"

if [[ ${baseline_status} -eq 0 ]]; then
[[ "${baseline_output}" == *'${{ matrix.foreign_output }}'* ]]
echo 'This hosted image already had a compatible foreign-architecture execution path before the workflow configured QEMU.' >>"${GITHUB_STEP_SUMMARY}"
elif [[ "${baseline_output}" == *"exec format error"* ]]; then
echo 'The baseline failed with the expected missing-emulation error.' >>"${GITHUB_STEP_SUMMARY}"
else
echo 'The baseline failed for a reason other than a recognized architecture mismatch.' >&2
exit 1
fi

- name: Set up foreign-architecture container emulation
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
with:
image: docker.io/tonistiigi/binfmt@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0
platforms: ${{ matrix.foreign }}

- name: Verify foreign-architecture container execution after QEMU setup
shell: bash
run: |
set -euo pipefail
foreign_architecture="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m)"
echo "Emulated container reported: ${foreign_architecture}"
[[ "${foreign_architecture}" == '${{ matrix.foreign_output }}' ]]
{
echo '## ${{ matrix.foreign }} container after explicit QEMU setup'
echo
echo "- Reported architecture: \`${foreign_architecture}\`"
echo '- Result: success'
} >>"${GITHUB_STEP_SUMMARY}"
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ That's it.

#### What Happens

EPAR initializes `.local/config.yml` for you if it does not exist. Docker-DinD is the default. The wizard asks whether new Docker-DinD runners should inherit the host's trusted TLS roots and defaults to yes. On native Windows, it also offers WSL2 when `wsl.exe --status` confirms default version 2; press Enter to keep Docker-DinD. Existing configs do not enable host trust inheritance automatically. You can customize the config afterward; see [Configuration](docs/configuration.md).
EPAR initializes `.local/config.yml` for you if it does not exist. Docker-DinD is the default. The wizard asks whether new Docker-DinD runners should inherit the host's trusted TLS roots and defaults to yes. On native Windows, it also offers WSL2 when `wsl.exe --status` confirms default version 2. On macOS, it offers experimental Tart mode when `tart --version` succeeds. Press Enter to keep Docker-DinD. Existing configs do not enable host trust inheritance automatically. You can customize the config afterward; see [Configuration](docs/configuration.md).

Then EPAR checks the configured runner image, builds or replaces it when needed, and starts the configured number of runners. The default config uses `pool.instances: 1`.

Expand Down Expand Up @@ -117,10 +117,12 @@ Docker-DinD is the default first choice. Other providers are available when they
| --- | --- |
| Docker-DinD | You have a Docker-compatible daemon on Windows, macOS, or Linux, and want a private Docker daemon per runner. |
| WSL2 | You are on Windows and want runners as disposable WSL distros. |
| Tart | You are on Apple Silicon macOS and want Linux VM runners; consider Docker-DinD first for Docker-heavy jobs because virtualization limits can affect compatibility. |
| Tart (experimental) | You are on Apple Silicon macOS and want to experiment with native ARM64 Linux VMs. The default Tart image is a basic Ubuntu OS image and does not include the normal GitHub-hosted runner dependency set. |

WSL2 also defaults to Catthehacker's full Ubuntu runner image, but it converts that Docker image into a WSL rootfs during `image build`.

Tart is not a ready-made substitute for GitHub's hosted Ubuntu runners. If you need that environment, build and maintain your own bootable Tart runner image by adapting the scripts from [actions/runner-images](https://github.com/actions/runner-images), then configure EPAR to use it. EPAR does not automate that conversion.

See [Usage](docs/usage.md) for WSL, Tart, source builds, custom configs, and advanced options.

## FAQ
Expand Down Expand Up @@ -154,10 +156,11 @@ GitHub also warns against using self-hosted runners with public repositories tha
- [GitHub App Setup](docs/github-app.md): required GitHub App permissions and fields.
- [Docker-DinD Provider](docs/providers/docker-dind.md): default Docker runner mode.
- [WSL Provider](docs/providers/wsl.md): Windows WSL2 runners.
- [Tart Provider](docs/providers/tart.md): Apple Silicon Linux VM runners.
- [Tart Provider (experimental)](docs/providers/tart.md): Apple Silicon ARM64 Linux VM runners and Rosetta compatibility limits.
- [Image Build](docs/image-build.md): image internals and customization.
- [Operations](docs/operations.md): logs, cleanup, and troubleshooting.
- [Troubleshooting](docs/troubleshooting.md): symptom-first diagnostics by host and provider.
- [Support](SUPPORT.md): where to start, what diagnostic information to collect, and where to ask for help.
- [Windows Startup](docs/advanced/windows-startup.md): start EPAR after Windows login.
- [macOS Startup](docs/advanced/macos-startup.md): start EPAR after macOS login.
- [Running EPAR Without Installing Go](docs/advanced/no-go-install.md): run from source with no local Go install.
Expand Down
18 changes: 18 additions & 0 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Support

Start with the [troubleshooting guide](docs/troubleshooting.md). It provides symptom-first diagnostics for Docker-DinD, WSL, Tart, host trust, image builds, storage, and cross-architecture containers.

Before asking for help, search the repository's existing [issues](https://github.com/solutionforest/ephemeral-action-runner/issues) and collect:

- the EPAR version or commit;
- the host operating system and architecture;
- the selected provider and runner image;
- the relevant configuration with credentials, private keys, tokens, certificate contents, organization names, and other sensitive values removed;
- the smallest reproducible workflow or command;
- the relevant controller, image-build, and guest logs with secrets removed.

For a reproducible bug or documentation gap, open a [GitHub issue](https://github.com/solutionforest/ephemeral-action-runner/issues/new/choose). For usage questions, include what you expected, what happened, and the diagnostics above so another contributor can reproduce the environment.

Do not report security vulnerabilities in a public issue. Follow the private reporting instructions in the [security policy](docs/security.md).

Community support is provided on a best-effort basis; there is no guaranteed response time or service-level agreement.
Loading
Loading