2026-10-20 · 6 min read · Rahul Chitturi
- portainer
- docker
- debugging
The Portainer env page turns KEY=value lines into a block you can paste, and it lists findings beside that block. A duplicate key produces a warning whose text says the later line overrides the earlier line. The block you copy keeps the first value and drops the later one.
That split is in the builder. The warning records the second line. The paste walks the entries and writes a key only the first time it appears. A stack deployed from the copy still has the earlier value.
Read the block, then read the warning
PORT=8080 followed by PORT=9090 warns Duplicate variable: PORT and says line 2 overrides line 1. The block is PORT=8080. SECRET= followed by SECRET=real-value warns that SECRET is empty and that line 2 overrides line 1. The block is SECRET= with nothing after the equals. The real value is not in the paste.
Quotes stay on the first value. PORT="8080" followed by PORT='9090' copies PORT="8080", quotes included. An invalid key is omitted and reported as an error, so a later valid duplicate is still judged against the first valid line. BAD KEY=2 between A=1 and A=3 does not become the value of A. The block keeps A=1.
- Diff the copied block against the file you meant to paste.
- Delete the earlier line when the later value is the one you want.
- Treat the override sentence as a description of the warning, not of the paste.
- Keep one line per key before you trust Needs review.
Comments, quotes, and placeholders stay literal
A line whose trim starts with # is skipped. export KEY=value drops the export prefix and one following space. A comment after a value is part of the value: NAME=app # note is copied unchanged. CRLF lines are split the same way as LF lines.
Placeholder checks strip one matching pair of quotes and then trim. 'change-me', Change-Me, and password warn. not-a-password does not, because the whole remaining value has to match. A value of a quoted space warns Empty value, and the block still contains the quotes. NEXT_PUBLIC_API_KEY, VITE_TOKEN, and PUBLIC_PASSWORD warn as public-looking secrets. NEXT_PUBLIC_URL does not, because the name lacks a secret-shaped word.
The Compose scan misses a no-colon default
The snippet scanner records ${NAME}, ${NAME:-default}, ${NAME:=x}, ${NAME:?msg}, and $NAME. A missing name is an error. $${NAME} still records NAME. $$NAME without braces does not. ${NAME-default} with a hyphen and no colon is not recorded, so a Compose default written that way will not show up as missing or as present.
Names that appear in the env block and not in the snippet are an info finding. Info does not increase Needs review. An empty snippet does not mark every variable unused. Use the Portainer env tool on a redacted sample, diff the paste against the source, the case tool when two keys differ only by case, and the YAML tool when the Compose snippet itself will not parse. Paste the block only after it contains the value you meant.