2026-10-20 · 8 min read
- portainer
- docker
- tooling
The Portainer env page parses KEY=value lines in the browser and builds a block plus a checklist. It also scans an optional Compose snippet for variable names. The findings list and the copied block do not use the same rule for duplicates.
Read the block before you paste it into Portainer. The warning text is a hint. The block is the value that will ship.
Keep the first line of a duplicate
A repeated key warns that the later line overrides the earlier line. The block writes each key once, from the first valid entry. PORT=8080 and then PORT=9090 copies PORT=8080. SECRET= and then SECRET=real-value copies SECRET= and also warns that the value is empty. The later secret is not in the paste.
An invalid key is an error and is left out, so it does not count as the first value. A=1, then BAD KEY=2, then A=3 still copies A=1 and warns that line 3 overrides line 1. Delete the line you do not want, then copy.
- One key, one line, before you copy.
- Put the intended value on the first line if you leave a duplicate in place.
- Diff the block against the source file.
- Treat Needs review as the count of errors and warnings.
Preserve quotes and trailing comments
Values are trimmed. Matching quotes are not removed in the block. PORT="8080" is copied with the quotes. Placeholder and empty checks do strip one matching quote pair and then trim, so 'change-me', Change-Me, and password warn, and a quoted space warns Empty value while the quotes remain in the block. not-a-password does not match the placeholder list.
Lines that trim to empty, and lines whose trim starts with #, are skipped. export PORT=8080 becomes PORT=8080. NAME=app # note keeps the comment in the value, because there is no second pass that strips inline comments. CRLF and LF both split into lines.
Match the Compose names the scanner can see
The scanner records ${NAME}, ${NAME:-default}, ${NAME:=value}, ${NAME:?message}, and $NAME. A name used in the snippet and missing from the env block is an error. $${NAME} still records NAME. $$NAME without a brace does not. ${NAME-default} with a hyphen and no colon is invisible to the scan, so that default will not be listed and will not be reported missing.
A variable present in the env block and absent from the snippet is info. Info is excluded from Needs review. With an empty snippet, nothing is marked unused. Keys must match a shell-style name: a letter or underscore, then letters, digits, or underscores. 1PORT and HAS-DASH are errors.
Paste a redacted block you have diffed
NEXT_PUBLIC_API_KEY, VITE_TOKEN, and PUBLIC_PASSWORD warn because the name looks public and secret-shaped. NEXT_PUBLIC_URL does not. The page still copies the values. Use a redacted sample in the browser and put real secrets into Portainer directly.
Use the Portainer env tool for the block and the findings, diff for the paste against the file, the case tool when two keys differ by case, and the YAML tool to see whether the Compose snippet parses. After the block matches the first value you intended, paste that block once.