Skip to content

Commit 03b8036

Browse files
authored
Merge pull request #4 from aaomidi/push-xrrtnzmqkoto
Fix Docker plugin builds to use arch-specific tags
2 parents 6bd5cf1 + ba7278f commit 03b8036

3 files changed

Lines changed: 141 additions & 66 deletions

File tree

.github/workflows/release.yml

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@ jobs:
1515
build:
1616
# Skip forks - they don't have write access to GHCR anyway
1717
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
18-
runs-on: ubuntu-latest
1918
permissions:
2019
contents: read
2120
packages: write
2221

2322
strategy:
2423
matrix:
25-
arch: [amd64, arm64]
24+
include:
25+
- arch: amd64
26+
runner: ubuntu-24.04
27+
- arch: arm64
28+
runner: ubuntu-24.04-arm
29+
30+
runs-on: ${{ matrix.runner }}
2631

2732
steps:
2833
- name: Checkout
@@ -33,9 +38,6 @@ jobs:
3338
with:
3439
go-version: "1.25"
3540

36-
- name: Set up QEMU
37-
uses: docker/setup-qemu-action@v3
38-
3941
- name: Set up Docker Buildx
4042
uses: docker/setup-buildx-action@v3
4143

@@ -49,66 +51,37 @@ jobs:
4951
- name: Build plugin rootfs
5052
run: |
5153
docker build \
52-
--platform linux/${{ matrix.arch }} \
53-
-t tslink:rootfs-${{ matrix.arch }} \
54+
-t tslink:rootfs \
5455
-f docker/Dockerfile \
5556
.
5657
5758
mkdir -p docker/rootfs
58-
docker create --name tslink-tmp tslink:rootfs-${{ matrix.arch }}
59+
docker create --name tslink-tmp tslink:rootfs
5960
docker export tslink-tmp | tar -x -C docker/rootfs
6061
docker rm tslink-tmp
6162
62-
- name: Create and push plugin
63-
run: |
64-
docker plugin create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-${{ matrix.arch }} docker/
65-
docker plugin push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-${{ matrix.arch }}
66-
67-
manifest:
68-
needs: build
69-
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
70-
runs-on: ubuntu-latest
71-
permissions:
72-
contents: read
73-
packages: write
74-
75-
steps:
76-
- name: Log in to GHCR
77-
uses: docker/login-action@v3
78-
with:
79-
registry: ${{ env.REGISTRY }}
80-
username: ${{ github.actor }}
81-
password: ${{ secrets.GITHUB_TOKEN }}
82-
83-
- name: Create and push PR manifest
63+
- name: Push PR plugin
8464
if: github.event_name == 'pull_request'
8565
run: |
86-
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }} \
87-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \
88-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-arm64
89-
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}
66+
docker plugin create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-${{ matrix.arch }} docker/
67+
docker plugin push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-${{ matrix.arch }}
9068
91-
- name: Create and push main manifest
69+
- name: Push main plugin
9270
if: github.ref == 'refs/heads/main'
9371
run: |
94-
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main \
95-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \
96-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-arm64
97-
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
72+
docker plugin create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ matrix.arch }} docker/
73+
docker plugin push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ matrix.arch }}
9874
99-
- name: Create and push version manifests
75+
- name: Push version plugin
10076
if: startsWith(github.ref, 'refs/tags/v')
10177
run: |
10278
VERSION=${GITHUB_REF#refs/tags/}
10379
104-
# Create versioned manifest
105-
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION} \
106-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \
107-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-arm64
108-
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}
109-
110-
# Create latest manifest
111-
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
112-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \
113-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-arm64
114-
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
80+
docker plugin create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}-${{ matrix.arch }} docker/
81+
docker plugin push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}
82+
83+
# Also push as latest-<arch>
84+
docker plugin disable ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}-${{ matrix.arch }} || true
85+
docker plugin rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-${{ matrix.arch }} || true
86+
docker plugin create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-${{ matrix.arch }} docker/
87+
docker plugin push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-${{ matrix.arch }}

README.md

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,20 @@ When you create a Docker network with this plugin and run containers on it:
4141

4242
### Install the Plugin
4343

44+
Docker plugins require architecture-specific tags:
45+
4446
```bash
45-
# Install latest stable release
46-
docker plugin install ghcr.io/aaomidi/tslink:latest
47+
# For amd64 (Intel/AMD, most cloud VMs)
48+
docker plugin install ghcr.io/aaomidi/tslink:latest-amd64
49+
50+
# For arm64 (Apple Silicon, AWS Graviton, Raspberry Pi)
51+
docker plugin install ghcr.io/aaomidi/tslink:latest-arm64
4752

4853
# Or install a specific version
49-
docker plugin install ghcr.io/aaomidi/tslink:v0.0.1
54+
docker plugin install ghcr.io/aaomidi/tslink:v0.0.1-amd64
5055

5156
# Or follow main branch (latest development)
52-
docker plugin install ghcr.io/aaomidi/tslink:main
57+
docker plugin install ghcr.io/aaomidi/tslink:main-amd64
5358

5459
# The plugin will be enabled automatically
5560
docker plugin ls
@@ -61,14 +66,15 @@ docker plugin ls
6166

6267
```bash
6368
# With auth key in command (ephemeral nodes by default)
69+
# Replace :latest-amd64 with :latest-arm64 for ARM systems
6470
docker network create \
65-
--driver ghcr.io/aaomidi/tslink:latest \
71+
--driver ghcr.io/aaomidi/tslink:latest-amd64 \
6672
--opt ts.authkey=tskey-auth-xxxxx \
6773
my-tailnet
6874

6975
# Or set the auth key globally when installing the plugin
70-
docker plugin set ghcr.io/aaomidi/tslink:latest TS_AUTHKEY=tskey-auth-xxxxx
71-
docker network create --driver ghcr.io/aaomidi/tslink:latest my-tailnet
76+
docker plugin set ghcr.io/aaomidi/tslink:latest-amd64 TS_AUTHKEY=tskey-auth-xxxxx
77+
docker network create --driver ghcr.io/aaomidi/tslink:latest-amd64 my-tailnet
7278
```
7379

7480
### Run Containers
@@ -96,11 +102,10 @@ curl http://web.your-tailnet.ts.net
96102
### Example: Docker Compose
97103

98104
```yaml
99-
version: '3.8'
100-
105+
# Use :latest-amd64 or :latest-arm64 depending on your system
101106
networks:
102107
tailnet:
103-
driver: ghcr.io/aaomidi/tslink:latest
108+
driver: ghcr.io/aaomidi/tslink:latest-amd64
104109
driver_opts:
105110
ts.authkey: ${TS_AUTHKEY}
106111

@@ -155,13 +160,13 @@ docker run -d --network my-tailnet \
155160
Instead of passing the auth key with each network, set it as a plugin environment variable:
156161

157162
```bash
158-
# Set default auth key
159-
docker plugin disable ghcr.io/aaomidi/tslink:latest
160-
docker plugin set ghcr.io/aaomidi/tslink:latest TS_AUTHKEY=tskey-auth-xxxxx
161-
docker plugin enable ghcr.io/aaomidi/tslink:latest
163+
# Set default auth key (use :latest-arm64 for ARM systems)
164+
docker plugin disable ghcr.io/aaomidi/tslink:latest-amd64
165+
docker plugin set ghcr.io/aaomidi/tslink:latest-amd64 TS_AUTHKEY=tskey-auth-xxxxx
166+
docker plugin enable ghcr.io/aaomidi/tslink:latest-amd64
162167
163168
# Now create networks without specifying the auth key
164-
docker network create --driver ghcr.io/aaomidi/tslink:latest my-tailnet
169+
docker network create --driver ghcr.io/aaomidi/tslink:latest-amd64 my-tailnet
165170
```
166171

167172
## Auth Key Types
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Spec: Docker Plugin Multi-Architecture Builds
2+
3+
> **Status:** Implemented
4+
> **Last Updated:** 2025-12-22
5+
6+
## Overview
7+
8+
The plugin is built for multiple CPU architectures (amd64, arm64) and published to GHCR. Due to Docker plugin registry limitations, each architecture is published as a separate tag rather than a unified multi-arch manifest.
9+
10+
## Goals
11+
12+
- Support both amd64 and arm64 architectures
13+
- Publish to GHCR on every push to main and on version tags
14+
- Enable PR testing with dedicated tags
15+
- Clear installation instructions for each architecture
16+
17+
## Non-Goals
18+
19+
- Unified multi-arch manifest (not supported by Docker plugins)
20+
- Support for additional architectures (386, ppc64le, etc.)
21+
- Automatic architecture detection during install
22+
23+
## Background: Docker Plugin Registry Format
24+
25+
Docker plugins use a different registry format than standard OCI container images:
26+
27+
| Aspect | Container Images | Docker Plugins |
28+
|--------|------------------|----------------|
29+
| Build command | `docker build` | `docker plugin create` |
30+
| Push command | `docker push` | `docker plugin push` |
31+
| Manifest support | Yes (OCI Image Index) | No |
32+
| Platform metadata | Embedded in manifest | Not present |
33+
34+
When attempting to create a manifest list for Docker plugins:
35+
36+
```
37+
docker manifest create ghcr.io/org/plugin:latest \
38+
ghcr.io/org/plugin:v1-amd64 \
39+
ghcr.io/org/plugin:v1-arm64
40+
```
41+
42+
The operation fails:
43+
44+
```
45+
manifest ghcr.io/org/plugin:v1-amd64 must have an OS and Architecture
46+
```
47+
48+
This occurs because `docker plugin push` does not include platform annotations in the registry blob, which `docker manifest` requires.
49+
50+
## Solution
51+
52+
Publish each architecture as a separate tag:
53+
54+
| Trigger | Tags Published |
55+
|---------|----------------|
56+
| Push to `main` | `:main-amd64`, `:main-arm64` |
57+
| Push tag `v0.0.1` | `:v0.0.1-amd64`, `:v0.0.1-arm64`, `:latest-amd64`, `:latest-arm64` |
58+
| Internal PR #123 | `:pr-123-amd64`, `:pr-123-arm64` |
59+
60+
Users specify their architecture explicitly:
61+
62+
```bash
63+
# amd64 (Intel/AMD, most cloud VMs)
64+
docker plugin install ghcr.io/aaomidi/tslink:latest-amd64
65+
66+
# arm64 (Apple Silicon, AWS Graviton)
67+
docker plugin install ghcr.io/aaomidi/tslink:latest-arm64
68+
```
69+
70+
## Build Pipeline
71+
72+
1. Matrix build runs for each architecture in parallel
73+
2. Native runners for each architecture (`ubuntu-24.04` for amd64, `ubuntu-24.04-arm` for arm64)
74+
3. Each job creates and pushes its architecture-specific tag
75+
4. No manifest job required
76+
77+
Both architectures build in ~1-2 minutes using native runners.
78+
79+
## Security Considerations
80+
81+
- **Fork PRs skipped**: Release workflow only runs for internal PRs (forks lack GHCR write access)
82+
- **Immutable version tags**: Version tags (`:v0.0.1-*`) are never overwritten
83+
- **Mutable development tags**: `:main-*` updated on each push to main
84+
- **Latest tracks releases**: `:latest-*` only updated on version tags, not main pushes
85+
86+
## Alternatives Considered
87+
88+
| Approach | Rejected Because |
89+
|----------|------------------|
90+
| OCI image wrapping plugin rootfs | Breaks standard `docker plugin install` workflow |
91+
| Single architecture only | arm64 increasingly common (Apple Silicon, Graviton) |
92+
| Separate repositories per arch | Confusing for users, harder to maintain |
93+
94+
## Future Enhancements
95+
96+
1. **Architecture detection script** - Helper script that detects arch and installs correct tag
97+
2. **OCI plugin format** - If Docker adds manifest support for plugins

0 commit comments

Comments
 (0)