Skip to content

chore(plane): update template to v1.3.1 with healthchecks and bug fixes#912

Open
leogomide wants to merge 7 commits into
Dokploy:canaryfrom
leogomide:update-plane-v1.3.1
Open

chore(plane): update template to v1.3.1 with healthchecks and bug fixes#912
leogomide wants to merge 7 commits into
Dokploy:canaryfrom
leogomide:update-plane-v1.3.1

Conversation

@leogomide

Copy link
Copy Markdown

What is this PR about?

Update the Plane template from v0.27.1 to v1.3.1 (latest stable), refactor the compose file for reliability, and fix several bugs in the current template.toml.

Image bumps

  • makeplane/plane-frontend, plane-space, plane-admin, plane-live, plane-backend, plane-proxy: v0.27.1v1.3.1
  • valkey/valkey: 7.2.5-alpine7.2.11-alpine
  • minio/minio: latestRELEASE.2025-04-22T22-12-26Z (pinned to the last release before MinIO removed the admin console from the community edition)
  • postgres, rabbitmq: kept (17-alpine, 3.13.6-management-alpine) to avoid breaking existing deployments

docker-compose.yml improvements

  • Add YAML anchors (x-db-env, x-redis-env, x-app-env, ...) and <<: [*anchor] merges to eliminate duplicated env across api/worker/beat-worker/migrator/live
  • Migrate from env_file: [.env] to inline environment: (matches the repo convention; Dokploy injects env via template.toml)
  • Add restart: unless-stopped to every long-running service; restart: on-failure on migrator
  • Add healthcheck to plane-db, plane-redis, plane-mq
  • api/worker/beat-worker now wait for migrator via condition: service_completed_successfully — fixes a race where the backend booted before the Django schema was migrated
  • proxy uses expose: ["80"] (no host port mapping, per the validator rule)

template.toml improvements (bug fixes)

  • RABBITMQ_DEFAULT_USER was the literal string "rabbitmq_user" instead of a variable reference, so the broker user ended up as rabbitmq_user while AMQP_URL expected something else. Now fixed to plane consistently.
  • WEB_URL=${Domain} was missing the https:// scheme, which broke OAuth callback URLs and email links emitted by the backend. Now WEB_URL=https://${main_domain}.
  • DATABASE_URL/AMQP_URL referenced inline vars (${POSTGRES_USER}, ${MINIO_ROOT_USER}) that Dokploy does not resolve inside [config.env] — only [variables] references and helpers work. Now built directly from [variables].
  • Remove dead vars: NGINX_PORT (Plane uses Caddy in v1.x), SENTRY_DSN, SENTRY_ENVIRONMENT.
  • Add WEBHOOK_ALLOWED_IPS="" and WEBHOOK_ALLOWED_HOSTS="" — Plane v1.x ships an SSRF guard for webhooks that blocks private IPs by default; exposing these as empty strings makes the intended posture explicit and discoverable.
  • Add APP_RELEASE to [variables] so users can pick the image tag via the Dokploy UI without editing env directly.

Logo

  • blueprints/plane/plane.png updated to the current Plane brand logo.

Checklist

  • I have read the suggestions in the README.md / CONTRIBUTING.md
  • I have validated the template locally (docker compose config, validate-docker-compose.ts, validate-template.ts all pass with zero errors)
  • meta.json processed with node build-scripts/process-meta.js (only the version bump appears in the diff)

Issues related

N/A — proactive maintenance update.

Testing notes

Validated locally with the repo's own scripts:

  • docker compose -f blueprints/plane/docker-compose.yml config → exit 0
  • tsx build-scripts/validate-docker-compose.ts → 13 services detected, structure valid
  • tsx build-scripts/validate-template.ts → 7 variables processed, structure valid

A full end-to-end smoke test (admin signup, project + issue + MinIO upload, live WebSocket "users online") can be run from the auto-generated PR preview.

- Bump makeplane/* images v0.27.1 -> v1.3.1
- Pin minio to RELEASE.2025-04-22T22-12-26Z (last release with full admin
  console before community-edition feature removal)
- Add YAML anchors (x-*-env) for env reuse across backend services
- Add healthchecks for postgres, valkey, rabbitmq
- api/worker/beat-worker wait for migrator via service_completed_successfully
  (fixes race where backend booted before schema was migrated)
- Add restart policies (unless-stopped for long-running, on-failure for migrator)
- Migrate env_file -> inline environment (matches repo convention)
- Expose APP_RELEASE in template.toml [variables] for UI override

Bug fixes in template.toml:
- RABBITMQ_DEFAULT_USER was the literal string "rabbitmq_user" instead of
  a variable reference; AMQP_URL was inconsistent with the broker user
- WEB_URL was missing the https:// scheme (broke OAuth/email links)
- DATABASE_URL/AMQP_URL referenced inline vars that Dokploy does not
  resolve inside [config.env]; now use [variables] directly
- Remove dead vars: NGINX_PORT (Plane v1.x uses Caddy), SENTRY_DSN,
  SENTRY_ENVIRONMENT
- Add WEBHOOK_ALLOWED_IPS/WEBHOOK_ALLOWED_HOSTS (SSRF guard default
  introduced in Plane v1.x)
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. version-bump labels May 28, 2026
@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
templates ✅ Ready (View Log) Visit Preview d651d2d

The plane-proxy image has hardcoded `reverse_proxy web:3000`/`api:8000`
in its Caddyfile with generic service names. In multi-stack Dokploy
deployments, those names collide with other stacks' containers (Next.js
apps frequently have a `web` service) on the shared dokploy-network —
the Plane domain ends up serving content from unrelated stacks.

Two-step fix:
  1. Rename internal services with `plane-*` prefix (unique cluster-wide):
     web/space/admin/live/api/worker/beat-worker/migrator → plane-*
  2. Add `links:` block to the `proxy` service mapping the new names back
     to the generic ones the Caddyfile expects (`plane-web:web` etc.).
     Docker injects these into /etc/hosts, which has absolute priority
     over DNS — Caddy resolves `web`/`api`/etc. directly to our renamed
     containers, ignoring any collision on dokploy-network.

Also updates `API_BASE_URL` in template.toml and the `x-live-env` anchor
from `http://api:8000` → `http://plane-api:8000`.

No `networks:` declarations added (repo validator rejects them; Dokploy
manages networking automatically).
@leogomide

Copy link
Copy Markdown
Author

Pushed a fix for a DNS collision bug in the internal proxy (commits 9c29df4 + 11bc16e).

Problem: the makeplane/plane-proxy image has reverse_proxy web:3000/api:8000 hardcoded in its Caddyfile. In multi-stack Dokploy deployments, those generic names collide with other stacks' containers on the shared dokploy-network (Next.js apps frequently have a web service) — the Plane domain ends up serving content from unrelated stacks.

Fix (two layers):

  1. Renamed the 8 internal services with a plane-* prefix (unique cluster-wide): web→plane-web, space→plane-space, admin→plane-admin, live→plane-live, api→plane-api, worker→plane-worker, beat-worker→plane-beat-worker, migrator→plane-migrator. Updated all depends_on cascades and the API_BASE_URL in template.toml + the x-live-env anchor to point at plane-api.
  2. Added a links: block on the proxy service mapping the new names back to the generic ones the Caddyfile expects (plane-web:web, plane-api:api, etc.). Docker injects these into /etc/hosts, which takes absolute priority over DNS — Caddy resolves web/api/etc. straight to our renamed containers, ignoring any collision.

No networks: declarations added — the repo validator (build-scripts/validate-docker-compose.ts:116-163) explicitly rejects them, and Dokploy manages networking automatically. The /etc/hosts approach solves the collision without needing network isolation.

Local validation (all 0 errors):

  • docker compose -f blueprints/plane/docker-compose.yml config → exit 0
  • tsx build-scripts/validate-docker-compose.ts -f blueprints/plane/docker-compose.yml → 13 services, valid
  • tsx build-scripts/validate-template.ts -d blueprints/plane → 7 vars, 1 domain, valid

@RDeluxe

RDeluxe commented Jun 3, 2026

Copy link
Copy Markdown

Hello! Thank you for your work!

I tried this on our instance, and got the following error in the proxy container.

"level":"info","ts":1780489811.5198905,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
{"level":"info","ts":1780489811.5205202,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":60627279052,"previous":9223372036854775807}
{"level":"info","ts":1780489811.520733,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
{"level":"info","ts":1780489811.521703,"msg":"maxprocs: No GOMAXPROCS change to reset"}
Error: adapting config using caddyfile: server block without any key is global configuration, and if used, it must be first
{"level":"info","ts":1780489813.504255,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
{"level":"info","ts":1780489813.5045652,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":60627279052,"previous":9223372036854775807}
{"level":"info","ts":1780489813.5053725,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
{"level":"info","ts":1780489813.5109265,"msg":"maxprocs: No GOMAXPROCS change to reset"}
Error: adapting config using caddyfile: server block without any key is global configuration, and if used, it must be first
{"level":"info","ts":1780489815.0965083,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
{"level":"info","ts":1780489815.0969374,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":60627279052,"previous":9223372036854775807}
{"level":"info","ts":1780489815.0970578,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
{"level":"info","ts":1780489815.099546,"msg":"maxprocs: No GOMAXPROCS change to reset"}
Error: adapting config using caddyfile: server block without any key is global configuration, and if used, it must be first
{"level":"info","ts":1780489817.7271638,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
{"level":"info","ts":1780489817.7275689,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":

Claude helped me pinpoint this to missing env variables in the proxy service. Here are the changes I made:

x-proxy-env: &proxy-env
  APP_DOMAIN: ${APP_DOMAIN:-localhost}
  FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880}
  BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads}
  SITE_ADDRESS: ${SITE_ADDRESS:-:80}          # ← added
  TRUSTED_PROXIES: ${TRUSTED_PROXIES:-0.0.0.0/0}  # ← added

And in my environment file I added

TRUSTED_PROXIES=0.0.0.0/0
SITE_ADDRESS=:80

Things are now working correctly.

Btw, and I'm unsure how to go about that, but I feel like a default env file with all the variables listed + comments, allowing the user to quickly fill them out would be nice. I used Claude again for this job (extracting the necessary variables)

…le crash

The plane-proxy Caddyfile.ce ends with a `{$SITE_ADDRESS} { import plane_proxy }`
block. SITE_ADDRESS has no inline default upstream, so when it is unset the block
becomes keyless and Caddy aborts with:

  adapting config using caddyfile: server block without any key is global
  configuration, and if used, it must be first

Expose SITE_ADDRESS (:80, matching the proxy `expose: ["80"]` + domain port 80,
since Dokploy terminates TLS upstream) and TRUSTED_PROXIES (0.0.0.0/0, matching
the Caddyfile inline default) on the proxy env, and document both in template.toml
so they render into the generated env. Reported and verified by @RDeluxe in Dokploy#912.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leogomide

Copy link
Copy Markdown
Author

Thanks a lot for testing on a real instance and pinpointing this, @RDeluxe! 🙏

You're exactly right. The makeplane/plane-proxy image's Caddyfile.ce ends with:

{$SITE_ADDRESS} {
    import plane_proxy
}

SITE_ADDRESS has no inline default upstream, so when it's unset the block becomes keyless and Caddy aborts with "server block without any key is global configuration, and if used, it must be first" — the error you saw. (TRUSTED_PROXIES does have an inline default of 0.0.0.0/0, but setting it explicitly makes the posture discoverable/overridable.)

I've applied your fix in 34501dd:

  • docker-compose.yml — added both vars to the x-proxy-env anchor:
    SITE_ADDRESS: ${SITE_ADDRESS:-:80}
    TRUSTED_PROXIES: ${TRUSTED_PROXIES:-0.0.0.0/0}
  • template.toml — added SITE_ADDRESS=:80 and TRUSTED_PROXIES=0.0.0.0/0 to [config.env] so they render into the env Dokploy generates.

:80 matches the proxy's expose: ["80"] and the domain bound to port 80 (Dokploy terminates TLS upstream via Traefik, so the proxy itself doesn't need ACME). Re-validated locally:

  • docker compose -f blueprints/plane/docker-compose.yml config → exit 0, SITE_ADDRESS: :80 resolved (no longer empty)
  • tsx build-scripts/validate-docker-compose.ts → 13 services, valid
  • tsx build-scripts/validate-template.ts → 7 vars, 1 domain, valid

Re: the default env file with comments — agreed it'd be a nice DX improvement, but I'll keep it out of this PR to stay focused on the version bump + fixes. Worth a follow-up. Thanks again!

…oofing)

Defaulting TRUSTED_PROXIES to 0.0.0.0/0 makes Caddy trust X-Forwarded-For /
X-Real-IP from any peer, allowing client-IP spoofing (CWE-348 / CWE-290) that
poisons rate-limiting and audit logs. In Dokploy the only upstream is Traefik on
the private dokploy-network, so trust Caddy's built-in `private_ranges` token
instead. Legitimate forwarded client IPs (Traefik is a private peer) still
resolve correctly; XFF from public-IP peers is rejected if the proxy is ever
exposed directly. Overridable via env for custom topologies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leogomide

Copy link
Copy Markdown
Author

Small follow-up / hardening on the TRUSTED_PROXIES value above (commit b24dbc7):

I changed the default from 0.0.0.0/0 to Caddy's built-in private_ranges token. 0.0.0.0/0 makes Caddy honor X-Forwarded-For/X-Real-IP from any peer, which allows client-IP spoofing (CWE-348/290) — poisoning rate-limits and audit logs if anything can reach the proxy directly.

Since Dokploy's only upstream is Traefik on the private dokploy-network, private_ranges keeps legitimate forwarded client IPs working exactly as before (Traefik is a private peer) while rejecting spoofed XFF from public-IP peers. It stays overridable via env for custom topologies. This doesn't touch SITE_ADDRESS=:80, so the original crash fix is unchanged. Re-validated (docker compose config + both repo validators) → all green.

…rrectly

The v1.3.1 rewrite (669ad4a) reordered template.toml so that
[[config.domains]] preceded the bare `env = [...]` and `mounts = []`
keys. In TOML those keys then attach to the first config.domains table
element instead of [config], leaving config.env undefined. Dokploy reads
env from config.env, so the base64 export / deploy shipped with no
environment variables at all.

Move env/mounts back above [[config.domains]] (matching the original
template and other blueprints like documenso). config.env now resolves
to all 39 entries; the domain table keeps only serviceName/port/host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leogomide

Copy link
Copy Markdown
Author

Pushed one more fix to this branch.

While testing the base64 template export, I noticed it shipped with no environment variables at all. Root cause was a TOML structure regression in the v1.3.1 rewrite: [[config.domains]] had been placed above the bare env = [...] / mounts = [] keys. In TOML, keys after a [[table]] header attach to that table element, so the whole env block ended up nested under config.domains[0].env and config.env was left undefined — Dokploy reads env from config.env, hence the empty export.

Fix: moved env/mounts back directly under [config], before [[config.domains]] (matching the original template and other blueprints like documenso). config.env now resolves to all 39 entries (including the SITE_ADDRESS / TRUSTED_PROXIES from the previous fix), and the domain table keeps only serviceName/port/host. Both repo validators still pass.

Add a "# ..." comment string before every entry in config.env, grouped
into labelled sections (Application, PostgreSQL, Redis, RabbitMQ,
storage, proxy, runtime) with blank-line separators. Because Dokploy
renders config.env entries verbatim into the generated .env, these
comment strings carry the documentation into the deployed .env file
(TOML # comments would be stripped at parse time and never reach it).

Same pattern already used by discord-tickets, garage-with-ui, tooljet
and rustfs. No variable values changed; all 39 vars preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files. version-bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants