plane-aio-commercial:stable — two defects: all Python services crash at import, and the image registers as Community edition
Summary
Two independent defects in makeplane/plane-aio-commercial:stable (v3.1.3):
- An incoherent OpenTelemetry dependency set crashes every Python service at import. The container serves the frontend but returns 502 on all API routes.
- The commercial image hardcodes the community startup path, so the instance self-registers as
PLANE_COMMUNITY.
Together these make the AIO Commercial image unusable as shipped. Defect 2 also blocks the mobile app, which requires Commercial edition.
Environment
|
|
| Image |
makeplane/plane-aio-commercial:stable |
| Index digest |
sha256:62c182da93e3c50ceb27d4b7361587bb02d155ba53f4f3b21963e513c565dafc |
| amd64 manifest |
sha256:28c2ad6be39869cb0a644fb990bda64a72a1fc458a72658ebae407a21877d431 |
| Reported version |
v3.1.3 (:stable and :v3.1.3 resolve to the same digest) |
| Runtime |
Podman 5.8.4, rootless |
| Data tier |
External Postgres 16, Redis 7, RabbitMQ 3, MinIO |
v3.1.2 reproduces defect 1 identically, so this is not a one-off bad build.
Defect 1 — OpenTelemetry version split crashes all Python services
Repro
No config, no network, no data tier required:
podman run --rm --entrypoint sh makeplane/plane-aio-commercial:stable \
-c 'cd /app/backend && python -c "import plane"'
Result
File "/app/backend/plane/celery.py", line 34, in <module>
from plane.observability.setup import configure_otel, flush_otel
File "/app/backend/plane/observability/setup.py", line 24, in <module>
from opentelemetry.instrumentation.django import DjangoInstrumentor
...
File "/usr/local/lib/python3.12/site-packages/opentelemetry/instrumentation/wsgi/__init__.py", line 250
from opentelemetry.semconv._incubating.attributes.user_agent_attributes import (
ImportError: cannot import name 'USER_AGENT_SYNTHETIC_TYPE' from
'opentelemetry.semconv._incubating.attributes.user_agent_attributes'
Cause
The image ships two different OTel release generations:
opentelemetry-api 1.28.1 <- 1.28 generation
opentelemetry-sdk 1.28.1 <- 1.28 generation
opentelemetry-semantic-conventions 0.49b1 <- pairs with 1.28.1
opentelemetry-instrumentation-* 0.65b0 <- pairs with 1.44.0
opentelemetry-exporter-otlp-proto-grpc 1.44.0 <- 1.44 generation
instrumentation-* 0.65b0 requires semantic-conventions 0.65b0; the image pins 0.49b1.
USER_AGENT_SYNTHETIC_TYPE does not exist in 0.49b1.
Impact
7 of 15 supervisord programs go FATAL: api, worker, beat, migrator,
outbox-poller, webhook-consumer, automation-consumer.
Because the migrator dies, no schema is ever created. Caddy still serves the
static frontend, so / and /god-mode/ return 200 while /api/instances/
returns 502 — the UI shell loads and then fails on every call, which makes this
present as a configuration problem to self-hosters.
Note on the OTEL_ENABLED gate
plane/observability/setup.py imports DjangoInstrumentor unconditionally at
module scope (line 24), and that module is reached from plane/__init__.py →
plane/celery.py on every entrypoint. The OTEL_ENABLED check never executes —
the crash happens at import time. Disabling OTel via configuration is not a
workaround.
Verified fix
Aligning semconv to the generation the instrumentation packages expect resolves it:
pip install --no-cache-dir "opentelemetry-semantic-conventions==0.65b0"
After this, import plane, import plane.celery, and
from plane.observability.setup import configure_otel all succeed, all 15
supervisord programs reach RUNNING, migrations run (406 applied, 299 tables),
and /api/instances/ returns 200.
Note this pulls opentelemetry-api 1.28.1 → 1.44.0 transitively, leaving
sdk at 1.28.1 and producing resolver warnings. A proper fix should align the
whole OTel set to one generation rather than patching a single package.
Defect 2 — the commercial image registers as Community edition
Observed
After defect 1 is worked around and the stack is healthy:
GET /api/instances/
"instance_name": "Plane Community Edition"
"edition": "PLANE_COMMUNITY"
Cause
The image ships four API entrypoints:
/app/backend/bin/docker-entrypoint-api.sh:39 python manage.py startup community --machine-signature "$MACHINE_SIGNATURE"
/app/backend/bin/docker-entrypoint-api-ee.sh:40 python manage.py startup commercial --machine-signature "$MACHINE_SIGNATURE"
/app/backend/bin/docker-entrypoint-api-cloud.sh:12 python manage.py startup cloud
/app/backend/bin/docker-entrypoint-api-local.sh
/app/supervisor.conf line 45 wires the community one:
[program:api]
command=sh -c "./bin/docker-entrypoint-api.sh"
The -ee variant appears to be the correct entrypoint for this image and is
present but unused.
Impact
plane/db/management/commands/startup.py branches on edition:
if edition == "community":
call_command("register_instance", machine_signature)
call_command("configure_instance")
call_command("create_bucket")
elif edition == "commercial":
call_command("register_instance_ee", machine_signature)
call_command("configure_instance")
call_command("create_bucket")
call_command("update_licenses")
So on the commercial image, register_instance_ee and update_licenses never
run. Downstream, workspace_governance_allowed is false,
prime_server_base_url is false, and latest_version reports v1.4.2
(older than the running v3.1.3), consistent with registration against the
wrong path.
Per the docs, the mobile app requires self-hosted Commercial v1.12.0+ and
returns 404 against Community — so as shipped, the AIO Commercial image cannot
serve the mobile app.
Question for maintainers
Is docker-entrypoint-api-ee.sh the intended entrypoint for this image, and is
the supervisor.conf reference simply wrong? Or does Commercial activation
require prime-server registration that the AIO image is not expected to perform
standalone? The docs describe AIO as Commercial Edition with the free plan
enabled by default, which suggests the former.
We deliberately did not switch entrypoints, since that is a
licensing-relevant change we are not in a position to make unilaterally.
Additional minor findings
- Docs/image port mismatch. The AIO docs say to publish SMTP ports
20025/20465/20587; the image actually exposes 10025/10465/10587.
/app/email/tls has no named volume in the documented run command, so it
mounts as an anonymous volume and TLS material is lost on container recreate.
Related issues
Suggested regression test
Both defects are catchable with a single check that needs no config, no network,
and no data tier:
podman run --rm --entrypoint sh makeplane/plane-aio-commercial:<tag> \
-c 'cd /app/backend && python -c "import plane"'
and one assertion on a booted instance:
curl -s localhost/api/instances/ | jq -r .instance.edition # expect PLANE_COMMERCIAL
The first would have caught #9572 and defect 1. The second would have caught
defect 2.
plane-aio-commercial:stable— two defects: all Python services crash at import, and the image registers as Community editionSummary
Two independent defects in
makeplane/plane-aio-commercial:stable(v3.1.3):PLANE_COMMUNITY.Together these make the AIO Commercial image unusable as shipped. Defect 2 also blocks the mobile app, which requires Commercial edition.
Environment
makeplane/plane-aio-commercial:stablesha256:62c182da93e3c50ceb27d4b7361587bb02d155ba53f4f3b21963e513c565dafcsha256:28c2ad6be39869cb0a644fb990bda64a72a1fc458a72658ebae407a21877d431v3.1.3(:stableand:v3.1.3resolve to the same digest)v3.1.2reproduces defect 1 identically, so this is not a one-off bad build.Defect 1 — OpenTelemetry version split crashes all Python services
Repro
No config, no network, no data tier required:
Result
Cause
The image ships two different OTel release generations:
instrumentation-* 0.65b0requiressemantic-conventions 0.65b0; the image pins0.49b1.USER_AGENT_SYNTHETIC_TYPEdoes not exist in0.49b1.Impact
7 of 15 supervisord programs go FATAL:
api,worker,beat,migrator,outbox-poller,webhook-consumer,automation-consumer.Because the migrator dies, no schema is ever created. Caddy still serves the
static frontend, so
/and/god-mode/return 200 while/api/instances/returns 502 — the UI shell loads and then fails on every call, which makes this
present as a configuration problem to self-hosters.
Note on the OTEL_ENABLED gate
plane/observability/setup.pyimportsDjangoInstrumentorunconditionally atmodule scope (line 24), and that module is reached from
plane/__init__.py→plane/celery.pyon every entrypoint. TheOTEL_ENABLEDcheck never executes —the crash happens at import time. Disabling OTel via configuration is not a
workaround.
Verified fix
Aligning semconv to the generation the instrumentation packages expect resolves it:
After this,
import plane,import plane.celery, andfrom plane.observability.setup import configure_otelall succeed, all 15supervisord programs reach RUNNING, migrations run (406 applied, 299 tables),
and
/api/instances/returns 200.Note this pulls
opentelemetry-api1.28.1 → 1.44.0 transitively, leavingsdkat 1.28.1 and producing resolver warnings. A proper fix should align thewhole OTel set to one generation rather than patching a single package.
Defect 2 — the commercial image registers as Community edition
Observed
After defect 1 is worked around and the stack is healthy:
Cause
The image ships four API entrypoints:
/app/supervisor.confline 45 wires the community one:The
-eevariant appears to be the correct entrypoint for this image and ispresent but unused.
Impact
plane/db/management/commands/startup.pybranches on edition:So on the commercial image,
register_instance_eeandupdate_licensesneverrun. Downstream,
workspace_governance_allowedisfalse,prime_server_base_urlisfalse, andlatest_versionreportsv1.4.2(older than the running
v3.1.3), consistent with registration against thewrong path.
Per the docs, the mobile app requires self-hosted Commercial v1.12.0+ and
returns 404 against Community — so as shipped, the AIO Commercial image cannot
serve the mobile app.
Question for maintainers
Is
docker-entrypoint-api-ee.shthe intended entrypoint for this image, and isthe supervisor.conf reference simply wrong? Or does Commercial activation
require prime-server registration that the AIO image is not expected to perform
standalone? The docs describe AIO as Commercial Edition with the free plan
enabled by default, which suggests the former.
We deliberately did not switch entrypoints, since that is a
licensing-relevant change we are not in a position to make unilaterally.
Additional minor findings
20025/20465/20587; the image actually exposes10025/10465/10587./app/email/tlshas no named volume in the documented run command, so itmounts as an anonymous volume and TLS material is lost on container recreate.
Related issues
v3.0.1 commercial AIO: every manage.py/celery process dies at import (bundled mcp 2.0.0 renamed streamablehttp_client) #9572 (open) — v3.0.1 commercial AIO: every manage.py/celery process dies
at import (bundled mcp 2.0.0 renamed streamablehttp_client). Same image, same
failure signature (all Python services dead at import, frontend still serving),
but a different mispinned dependency. Defect 1 above is the same class of bug
recurring in v3.1.3 with
opentelemetry-semantic-conventionsinstead ofmcp.This suggests the AIO Commercial image's dependency set is not pinned or
smoke-tested against a plain
import planebefore release.[bug]: AIO commercial image ships broken entrypoint scripts (missing plane user and su-exec) #8710 (closed) — AIO commercial image ships broken entrypoint scripts
(missing plane user and su-exec). Prior art for defect 2: the AIO Commercial
entrypoint layer has shipped broken before.
Suggested regression test
Both defects are catchable with a single check that needs no config, no network,
and no data tier:
and one assertion on a booted instance:
The first would have caught #9572 and defect 1. The second would have caught
defect 2.