Portainer Swarm node labels and CPU limits for a low-traffic stack: which constraint actually controls placement?
0 reputation · 16 Sept 2020, 16:05 UTC
0 reputation · 16 Sept 2020, 16:05 UTC
I'm consolidating a low-traffic internal stack onto cheaper nodes in a Docker Swarm managed through Portainer. The plan is to label two small nodes (for example tier=low) and use placement constraints plus CPU/memory limits in the stack definition so these services never land on the larger nodes and never consume more than a fixed share of resources.
What I'm unsure about is how Portainer actually applies these settings when a stack is deployed through its UI or GitOps integration. Specifically, whether placement constraints defined in the compose file are preserved as-is, and whether the resource limits I set in Portainer's container/stack editor override or merge with what's in the compose file.
I'm also unclear on the failure behavior: if no node with the matching label has capacity, does the service stay pending, or does Swarm schedule it elsewhere?
My questions:
deploy.placement.constraints and deploy.resources.limits take precedence over values set in the Portainer UI, or vice versa?When you deploy a stack through Portainer, the deploy.placement.constraints and deploy.resources.limits defined in the Compose file are the source of truth. Editing those values in Portainer’s stack editor updates the Compose file itself, so the UI values take precedence only because they replace the Compose content. If you change constraints or limits on a running service via the Service → Details page, Portainer creates a runtime override that lasts until the next stack redeploy, at which point the Compose file values are reapplied.
node.labels.tier==low). Only nodes that satisfy all constraints remain eligible.resources.limits and resources.reservations. If a node lacks sufficient capacity, it is treated as ineligible.Therefore, the node‑label constraint is the decisive filter; CPU limits act only as a secondary feasibility check.
Portainer shows a node’s labels on the Nodes → <node‑name> → Labels tab. This view mirrors the output of docker node inspect <node-id> and is the supported way to confirm which labels a node carries before scheduling.
docker node update --label-add tier=low <node-id> (or do it via Portainer → Nodes → Edit).deploy:
placement:
constraints:
- node.labels.tier==low
resources:
limits:
cpus: '0.5'
memory: 256M
docker service ps <service-name>.The described behavior has been unchanged in Portainer CE 2.x and BE releases up to at least 2.19.0 (the latest as of 2026‑09). No recent release altered the way Compose‑defined constraints or limits are merged with UI edits, so you can rely on this behavior across recent versions.
Are you planning to adjust constraints/limits via the Stack editor (which updates the Compose file) or via the Service → Details page (which creates a temporary override)? The answer determines whether your changes will persist after a redeploy.
Use comments to ask for clarification. Post a solution as an answer.
6,400 reputation · 17 Sept 2020, 02:20 UTC
Worth separating two fields that are easy to conflate: deploy.resources.limits are runtime caps enforced by the node’s cgroups, while deploy.resources.reservations are what the scheduler evaluates during placement. A service with a CPU limit but no reservation can still land on a labeled node that is already saturated, then be throttled or killed by the cgroup cap.
For a low-traffic stack, set reservations.cpus alongside limits.cpus so the label constraint plus reservation acts as the real gate. If you deploy via Portainer’s GitOps path, the repository definition is authoritative during reconciliation; UI-only edits to constraints or resources can be reverted on the next sync unless committed. Verify on your exact Portainer build whether such edits persist after a sync. After deployment, verify the effective spec with docker service inspect, checking Placement.Constraints and Resources.Reservations separately from Resources.Limits.