SOLUTION - Group PyArmor license on Windows Host - Run unlimited dockers in offline device
After reading the specific documentation https://pyarmor.readthedocs.io/en/stable/how-to/register.html#run-unlimited-dockers-in-offline-device, I encountered several problems trying to apply it to my environments (Windows host, WSL & docker desktop, Linux containers). I read the previous open issues but did not find a working solution. So, after several attempts, I found a working solution, which I describe below, hoping it will be useful.
Linked issues:
- https://github.com/dashingsoft/pyarmor/issues/2217
- https://github.com/dashingsoft/pyarmor/issues/2214
- https://github.com/dashingsoft/pyarmor/issues/2212
- https://github.com/dashingsoft/pyarmor/issues/2196
- https://github.com/dashingsoft/pyarmor/issues/2141
and more...
PyArmor Group on Windows + Docker Desktop (centralized proxy)
This guide explains how to reliably use a PyArmor Group license with Docker Desktop on Windows using the official "Run unlimited dockers in offline device" approach. It describes the initial scenario, the centralized proxy solution implemented in this repo, and the essential operational steps.
Initial scenario
- Windows host with Docker Desktop (Linux containers), registered as an offline PyArmor device (Windows device regfile).
- Requirement: run PyArmor inside a Docker container while reusing the host license.
- Problem: on Docker Desktop,
host.docker.internalis not in the same L3 subnet as containers; PyArmor requires an on-net (same-subnet) auth endpoint for the unlimited-dockers mode.
Adopted solution (in this project)
pyarmor-authruns on Windows using the Windows device regfile.- A centralized TCP proxy (socat-based) runs in a shared Docker network
pyarmor-net(172.30.0.0/24) with a fixed IP 172.30.0.10. - Each application stack attaches to
pyarmor-netand mapshost.docker.internal-> 172.30.0.10. This makes the auth endpoint appear on-net and license checks succeed.
Repo components:
- Substack:
pyarmor-proxy/docker-compose.yml(createspyarmor-netand starts the central proxy at 172.30.0.10) - Application stacks:
your-app-image,your-app-image2,... (attached topyarmor-netwithextra_hostspointing to the proxy)
Prerequisites
- PyArmor installed on Windows and a valid Group license (activation/registration files).
- Docker Desktop running on Windows.
- PowerShell (pwsh) recommended as your shell.
Step-by-step
- Start the PyArmor auth server on Windows (using the Windows device regfile):
pyarmor-auth C:\path\to\pyarmor-device-regfile-xxxx.N.zip
# Expected: "listen container auth request on 0.0.0.0:29092"
- Start the centralized proxy (also creates the shared
pyarmor-netif missing):
pyarmor-proxy/docker-compose.yml
FROM alpine:3.19
RUN apk add --no-cache socat
EXPOSE 29092
# Forward local 29092 to Windows host via Docker Desktop mapping
ENTRYPOINT ["socat","-d","-d","TCP-LISTEN:29092,fork,reuseaddr","TCP:host.docker.internal:29092"]
(docker network inspect pyarmor-net >/dev/null 2>&1) || docker network create --subnet=172.30.0.0/24 pyarmor-net
docker compose -f ./pyarmor-proxy/docker-compose.yml up -d --build
- Start an application stack (already configured to use the proxy):
docker compose -f ./your-app-image/docker-compose.yml up -d --build
- Register PyArmor inside the container with the Windows device regfile (if not already done there):
docker cp "C:\path\to\pyarmor-device-regfile-xxxx.N.zip" your-app-image:/
docker exec -it your-app-image bash
pyarmor reg /home/coder/project/pyarmor-device-regfile-xxxx.N.zip
pyarmor -v
- Quick checks
ping -c 1 host.docker.internal
curl -v telnet://host.docker.internal:29092
echo "print('hello world')" > foo.py
pyarmor gen --enable-rft foo.py
How it works (why the proxy is needed)
- On Docker Desktop, the IP behind
host.docker.internalis typically not in the same subnet as containers. PyArmor requires an on-net endpoint for unlimited-dockers. - The centralized proxy lives in the same Docker network as the app containers (
pyarmor-net) and forwards tohost.docker.internal:29092(routed to Windows by Docker Desktop). PyArmor thus sees a peer in the same network and license verification succeeds.
Minimal example (app service)
Application stacks use a shared external network and map the host to the proxy:
services:
app:
image: your-app-image
extra_hosts:
# Map host.docker.internal to the on-net PyArmor proxy (same-subnet requirement)
- "host.docker.internal:172.30.0.10"
networks:
# Attach pyarmor-net first (primary) so default route matches the host mapping
- pyarmor-net
networks:
pyarmor-net:
external: true
Notes:
- If 172.30.0.0/24 conflicts in your environment, adjust the subnet in
pyarmor-proxy/docker-compose.ymland updateextra_hostsin your stacks. - Ensure
pyarmor-authis reachable on TCP/29092 (Windows firewall rules).
Troubleshooting
-
"this license is not for this machine" during
gen:- Verify reachability to the proxy:
ping host.docker.internal,curl -v telnet://host.docker.internal:29092. - Ensure
pyarmor-authuses the Windows device regfile. - Inside the container,
pyarmor -vshould show a valid Group license for your product.
- Verify reachability to the proxy:
-
"out of license":
- PyArmor is not registered in the container or cannot reach the auth server. Register with the correct device regfile and check connectivity.
-
Versions:
- Keep PyArmor versions aligned between host and container (same major recommended). Aligning Python versions is not strictly required for this solution, but generally reduces runtime differences.
@DarioMagniPLRM Thanks for your contributions.
While I reproduce this on MacOS, I encounter some problems. I am using orbstack which is a lightweight docker implementation. The pyarmor container ip is 192.168.139.149, proxy container ip is 192.168.139.164. And a socat is running on proxy container to forward traffic to macos.
On pyarmor container: pyarmor reg ./pyarmor-device-regfile-6694.8.zip INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.aarch64 INFO register "./pyarmor-device-regfile-6694.8.zip" INFO machine id in group license: m71da5a46c4b68b964df1768169779c0a INFO no machine id matchs this group license INFO take this machine as docker container, and connect to docker host for authentication... INFO socket addr: ('192.168.139.149', 51040) INFO remote addr: ('192.168.139.164', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e INFO This license registration information:
License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx
BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No
Notes
- Offline obfuscation 4140.8
seems reg success.
pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx
License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx
BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No
Notes
- Offline obfuscation 4140.8
INFO socket addr: ('192.168.139.149', 53320) INFO remote addr: ('192.168.139.164', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e
However, pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.aarch64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources ERROR this license is not for this machine
cat pyarmor.bug.log [BUG]: this license is not for this machine
Command Line
xxx/pyarmor gen --enable-rft foo.py
Environments
Python 3.12.12 Pyarmor 9.1.8 (group), 006694, xxx Platform linux.aarch64 Native linux.aarch64 Home /home/xxx/.pyarmor
Traceback
Traceback (most recent call last):
File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 793, in main
main_entry(sys.argv[1:])
File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 786, in main_entry
return args.func(ctx, args)
^^^^^^^^^^^^^^^^^^^^
File "xxx/lib/python3.12/site-packages/pyarmor/cli/main.py", line 248, in cmd_gen
builder.process(options)
File "xxx/lib/python3.12/site-packages/pyarmor/cli/generate.py", line 179, in process
Pytransform3.pre_build(self.ctx)
File "xxx/lib/python3.12/site-packages/pyarmor/cli/core/init.py", line 110, in pre_build
return m.pre_build(ctx)
^^^^^^^^^^^^^^^^
File "
As far as I know, the connectivity is ok, and they share same subnet mask. Any suggestions?
I found that I can directly access host(macos) from pyarmor docker, however the output is still the same.
ifconfig on docker: docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255 ether f2:95:70:49:95:f2 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 2 overruns 0 carrier 0 collisions 0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.139.149 netmask 255.255.255.0 broadcast 192.168.139.255
inet6 fe80::3c09:d2ff:feec:308a prefixlen 64 scopeid 0x20
inet6 fd07:b51a:cc66:0:3c09:d2ff:feec:308a prefixlen 64 scopeid 0x0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
ifconfig on macos: bridge100: flags=8a63<UP,BROADCAST,SMART,RUNNING,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500 options=63<RXCSUM,TXCSUM,TSO4,TSO6> ether 7e:61:30:0e:53:64 inet 192.168.139.3 netmask 0xfffffe00 broadcast 192.168.139.255 inet6 fe80::7c61:30ff:fe0e:5364%bridge100 prefixlen 64 scopeid 0x14 inet6 fd07:b51a:cc66:0:a617:db5e:ab7:e9f1 prefixlen 64 Configuration: id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 ipfilter disabled flags 0x0 member: vmenet0 flags=10003<LEARNING,DISCOVER,CSUM> ifmaxaddr 0 port 19 priority 0 path cost 0 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active
pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx
License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx
BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No
Notes
- Offline obfuscation 4140.9
INFO socket addr: ('192.168.139.149', 43582) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e
every reboot the docker machine id will change. does that means I need to add it someway to the host regfile? it is not in the list above.
@BCDS-haoran
Please check this guide https://pyarmor.readthedocs.io/en/latest/how-to/register.html#run-unlimited-dockers-in-offline-device
@jondy Hi, thanks for quick reply. I've already checked the docs and existing issues. However, it seems the situation is still the same. Does that means pyarmor-auth just cannot running on Macos, even I have same network mask with the docker container? Or did I miss something? :)
@BCDS-haoran
I don't what's exactly your problem.
pyarmor-auth must be run in one machine which machine id is stable, and it must be in this machine to run docker container.
And the network mask should be same, and ttl no more than 2
@jondy the pyarmor-auth is running in macos host machine, and its machine id is stable. The host machine(192.168.139.3) runs docker container(192.168.139.149), they share same network mask as shown in ifconfig above.
Inside docker container, I ran traceroute:
traceroute 192.168.139.3 traceroute to 192.168.139.3 (192.168.139.3), 30 hops max, 60 byte packets 1 192.168.139.3 (192.168.139.3) 0.288 ms 0.195 ms 0.271 ms
It seems all config is ok, but I got "RuntimeError: this license is not for this machine"
In docker container, try to export one environment variable
export PYARMOR_DOCKER_HOST=192.168.139.3
Or make sure domain name host.docker.internal is parsed as 192.168.139.3
Then run pyarmor gen ... to check it.
@jondy Yes I have already set the host.docker.internal to 192.168.139.3 through editing /etc/hosts, and also export the env variable.
on host machine, I ran pyarmor-auth: pyarmor-auth ./pyarmor-device-regfile-6694.8.zip 2025-11-12 16:08:41,637: work path: /Users/xxx/.pyarmor/docker 2025-11-12 16:08:41,638: register "pyarmor-device-regfile-6694.8.zip" 2025-11-12 16:08:41,645: machine id in group license: m71da5a46c4b68b964df1768169779c0a 2025-11-12 16:08:41,647: this machine id matchs group license 2025-11-12 16:08:41,650: listen container auth request on 0.0.0.0:29092 2025-11-12 16:09:11,562: receive request from ('192.168.139.57', 39044) 2025-11-12 16:09:11,563: send auth result to ('192.168.139.57', 39044) 2025-11-12 16:09:17,065: receive request from ('192.168.139.57', 39056) 2025-11-12 16:09:17,065: send auth result to ('192.168.139.57', 39056)
on docker container, I ran
pyarmor reg ./pyarmor-device-regfile-6694.8.zip INFO Python 3.12.12 INFO Pyarmor 9.1.8 (trial), 000000, non-profits INFO Platform linux.x86_64 INFO register "./pyarmor-device-regfile-6694.8.zip" INFO machine id in group license: m71da5a46c4b68b964df1768169779c0a INFO no machine id matchs this group license INFO take this machine as docker container, and connect to docker host for authentication... INFO socket addr: ('192.168.139.57', 39044) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e INFO This license registration information:
License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx
BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No
Notes
- Offline obfuscation 4140.8
pyarmor -v Pyarmor 9.1.8 (group), 006694, xxx
License Type : pyarmor-group License No. : pyarmor-vax-006694 License To : xxx License Product : xxx
BCC Mode : Yes RFT Mode : Yes CI/CD Mode : No
Notes
- Offline obfuscation 4140.8
INFO socket addr: ('192.168.139.57', 39056) INFO remote addr: ('192.168.139.3', 29092) INFO got docker host machine id: m71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: l71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: i71da5a46c4b68b964df1768169779c0a INFO got docker host machine id: k357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: g357ebdbdeb352fab7b3808dc99acaccc INFO got docker host machine id: b100bd8874c6cf987c86751914b9a8d1e
pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.x86_64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources ERROR this license is not for this machine
Connection is ok, but it just cannot recognize the license.
In the docker container, what's the output of tracert:
$ tracert 192.168.139.3
It still has TTL limits for pyarmor gen command, but pyarmor reg no this limits.
Here's the output of traceroute traceroute 192.168.139.3 traceroute to 192.168.139.3 (192.168.139.3), 30 hops max, 60 byte packets 1 192.168.139.3 (192.168.139.3) 0.618 ms 0.259 ms 0.185 ms
Is there /dev/disk in the docker container?
If it exists, rename it to any other name. For example, mv /dev/disk /dev/no-disk
after that, the pyarmor gen command run succeed
pyarmor gen --enable-rft foo.py INFO Python 3.12.12 INFO Pyarmor 9.1.8 (group), 006694, xxx INFO Platform linux.x86_64 INFO search inputs ... INFO find script foo.py INFO find 1 top resources INFO start to generate runtime files INFO target platforms {'linux.x86_64'} INFO write dist/pyarmor_runtime_006694/pyarmor_runtime.so INFO generate runtime files OK INFO start to obfuscate scripts INFO process resource "foo" INFO obfuscating file foo.py INFO write dist/foo.py INFO obfuscate scripts OK
thanks a lot!