Forking and adding a server
Drop a new server into the suite — a Dockerfile, a compose entry, an action.yml wait step. The repo was designed to be extended this way.
The repo is tiny by design. Adding a server is four small edits.
What the repo looks like
extra-test-suite/
├── README.md
├── CHANGELOG.md
├── action.yml
├── docker-compose.yml
├── docker-compose.ci.yml
├── open62541-nm/
│ └── Dockerfile
├── open62541-all-security/
│ ├── Dockerfile
│ ├── entrypoint.sh
│ └── server.c
└── .github/workflows/
└── publish.yml
Each server is one directory containing a Dockerfile and
whatever extra files it needs.
Step 1 — Create the service directory
From the root of your forked extra-test-suite/ checkout:
mkdir my-new-server
cd my-new-server
Inside that directory, write a Dockerfile. The two existing
ones are templates:
| Source you want to base on | Look at |
|---|---|
| open62541 with custom build flags | open62541-nm/Dockerfile |
| open62541 with custom server.c | open62541-all-security/Dockerfile |
| A different OPC UA stack (Milo, Prosys, …) | Build from a base image yourself |
Both existing Dockerfiles use:
- Multi-stage build — build stage uses
debian:bookworm-slimwith build tools; runtime stage strips everything except the binary. ARG OPEN62541_REF=v1.4.8— pin the upstream version.EXPOSE 4840— internal port is always 4840.- OCI labels —
org.opencontainers.image.sourceetc. for GHCR.
A minimal Dockerfile template:
FROM debian:bookworm-slim AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
git cmake build-essential ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# ... build steps producing /usr/local/bin/myserver ...
FROM debian:bookworm-slim
LABEL org.opencontainers.image.source="https://github.com/<owner>/extra-test-suite"
LABEL org.opencontainers.image.description="My custom OPC UA test server"
LABEL org.opencontainers.image.licenses="MIT"
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/local/bin/myserver /usr/local/bin/
EXPOSE 4840
CMD ["/usr/local/bin/myserver"]
The two existing Dockerfiles in this repo don't follow this
shape exactly — they COPY --from=build /src/build/bin/examples/<binary>
because open62541's CMake build drops example binaries there.
If you're forking from one of those Dockerfiles, either move
your binary to /usr/local/bin/ during the build stage so the
runtime COPY above works, or change the runtime COPY source
path to wherever your build stage actually produces the binary.
Step 2 — Add the service to docker-compose.yml
Pick a host port that doesn't conflict with anything already in use:
4840-4849 reserved for uanetstandard-test-suite
4851 reserved for uanetstandard-test-suite (SKS)
14850 reserved for uanetstandard-test-suite (PubSub)
24840 taken by open62541-nm
24841 taken by open62541-all-security
24842+ available
For convenience, follow the 2484x convention. Add to
docker-compose.yml:
services:
open62541-nm:
# ... existing ...
open62541-all-security:
# ... existing ...
my-new-server:
image: ghcr.io/<owner>/extra-test-suite/my-new-server:${TAG:-latest}
build:
context: ./my-new-server
container_name: opcua-extra-my-new
ports:
- "24842:4840"
restart: unless-stopped
The image: + build: dual pattern means:
docker compose up -d --buildin local dev → builds from the Dockerfile (force).docker compose pull && docker compose up -din CI → pulls from GHCR.- Plain
docker compose up -d(no flag) prefers theimage:if it's available locally or in the registry, and only falls back tobuild:when neither has a copy — so pass--buildexplicitly when you want to test your local Dockerfile changes.
Step 3 — Add to docker-compose.ci.yml
The CI override disables auto-restart for every service:
services:
open62541-nm:
restart: "no"
open62541-all-security:
restart: "no"
my-new-server:
restart: "no"
Step 4 — Add a port-wait to action.yml
The composite action polls each service's port. Add yours:
rc=0
wait_port open62541-nm 24840 || rc=1
wait_port open62541-all-security 24841 || rc=1
wait_port my-new-server 24842 || rc=1 # <-- add
if [ "$rc" -ne 0 ]; then
docker compose -f "$COMPOSE_FILE" -f "$COMPOSE_CI_FILE" logs || true
exit "$rc"
fi
Step 5 — Bump the minor version
Adding a service is a minor bump per the suite's versioning rules.
v1.1.0 → v1.2.0
Tag and push:
git tag v1.2.0
git push origin v1.2.0
The publish workflow runs on v* tag push:
docker compose build --push— builds every service tagged with:v1.2.0and uploads them to GHCR in one step.- Re-tags and pushes each image as
:latest.
Both your existing services and the new one get published in one workflow run — no per-service workflow changes needed.
When to add a server
| Scenario | Add a server? |
|---|---|
You found a gap in uanetstandard-test-suite coverage |
Yes |
| You need a vendor-specific stack (Prosys, Milo, …) in CI | Yes |
| You need a different version of open62541 | Yes (new service) |
| You need a different security config of the same stack | Yes (new service) |
| You want a richer address space | No — fork uanetstandard-test-suite and add a builder |
The suite stays small because the main suite is the right place for most things. The "extras" are explicitly the things the main suite doesn't or can't cover.
Common gaps that could become services
| Gap | Possible service |
|---|---|
| Prosys-style rich type model | A Prosys Simulation Server image |
| Eclipse Milo (Java stack) | A Milo demo server image |
| node-opcua wire details | A node-opcua-based server |
| MQTT-PubSub | A separate broker + a PubSub publisher |
| Vendor-specific ExtensionObject payloads | Whatever vendor stack ships those types |
For a real fork-and-PR scenario, see the suite's GitHub issues for what the maintainers are considering.
Where to read next
- Servers · Overview — the existing services for reference.
- GitHub Action — the action your new service plugs into.