devcontainers / devcontainers/cli
Multi-variable ARG declared via line continuation loses its second variable
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 457
- Avg merge
- 13h 17m
- Merged PRs (30d)
- 6
Description
## Summary
When a Dockerfile declares two `ARG`s using a single instruction with a backslash line continuation (valid, real Docker/BuildKit syntax):
```dockerfile
ARG VERSION_UNUSED=x \
VERSION_BASE=latest
FROM alpine:${VERSION_BASE}
```
`docker build` resolves `${VERSION_BASE}` to `latest` correctly. But `@devcontainers/cli`'s own lightweight Dockerfile scanner (used to pre-fetch base-image details before invoking the real build) never sees `VERSION_BASE` as a declared `ARG` at all, so `${VERSION_BASE}` resolves to an **empty string**. The image reference becomes `alpine:` (no tag), which is rejected by the Docker daemon as `invalid reference format`. In our case this made `Reopen in Container` fail outright, before the actual build (which would have succeeded) ever ran.
## Environment
- Dev Containers extension: 0.466.0
- VS Code: 1.134.0
- `@devcontainers/cli` (bundled): 0.88.0
- Docker Desktop: 4.87.0 (Engine 29.7.2)
- macOS (arm64)
## Minimal reproduction
`.devcontainer/Dockerfile`:
```dockerfile
ARG VERSION_UNUSED=x \
VERSION_BASE=latest
FROM alpine:${VERSION_BASE}
```
`.devcontainer/devcontainer.json`:
```jsonc
{
"name": "arg-continuation-repro",
"dockerComposeFile": "../docker-compose.yml",
"service": "web",
"workspaceFolder": "/workspace"
}
```
`docker-compose.yml`:
```yaml
services:
web:
build:
context: .
dockerfile: .devcontainer/Dockerfile
args:
VERSION_BASE: "3.20"
command: ["sleep", "infinity"]
```
Steps:
1. Save the three files above in an otherwise empty folder.
2. Open the folder in VS Code and run **Dev Containers: Reopen in Container**.
## Expected result
The container builds using `alpine:3.20` (from the compose `build.args` override) or `alpine:latest` (the Dockerfile default) — either way, a valid tag.
## Actual result (from our real project, same root cause, different image name)
```
[...] Start: Run: docker inspect --type image futureys/gatsby:
[...] Stop (11 ms): Run: docker inspect --type image futureys/gatsby:
[...] Error fetching image details: No manifest found for docker.io/futureys/gatsby:.
[...] Start: Run: docker pull futureys/gatsby:
[...] invalid reference format
[...] Stop (137 ms): Run: docker pull futureys/gatsby:
[...] Retrying (Attempt 0) with error
'Command failed: docker pull futureys/gatsby:
'
... (retries 1-4, same error) ...
[...] Command failed: docker inspect --type image futureys/gatsby:
[...] []
Error response from daemon: invalid reference format
[...] Command failed: docker pull futureys/gatsby:
Error: Command failed: docker pull futureys/gatsby:
```
`Reopen in Container` fails with this error before the real `docker compose build` is ever invoked, even though `docker build` on the same Dockerfile (outside of Dev Containers) succeeds and resolves the ARG correctly.
## Root cause
The bundled `devContainersSpecCLI.js` parses ARG/ENV/USER declarations with this regex (used to compute the default value of an `ARG` referenced in `FROM ...:${ARG}` before the real build runs, e.g. to fetch base-image metadata):
```
/^\s*(?ARG|ENV|USER)\s+(?[^\s=]+)([ =]+("(?\S+)"|(?\S+)))?/gmi
```
Because of the `m` (multiline) flag, `^` anchors to the start of *each physical line*. For:
```dockerfile
ARG VERSION_UNUSED=x \
VERSION_BASE=latest
```
only the first line matches (`name=VERSION_UNUSED`). The second physical line (` VERSION_BASE=latest`) doesn't start with `ARG`/`ENV`/`USER`, so it's invisible to this scanner — even though real Docker/BuildKit joins the two lines via the trailing `\` into one `ARG` instruction that declares **both** variables (verified: `docker build` on the same Dockerfile resolves `${VERSION_BASE}` to `latest`/the passed `--build-arg` correctly).
Downstream, the variable-resolution walk (roughly `Iv` → `wg` → `LV` → `Cv` in the minified bundle) looks for an instruction whose `name` matches the referenced variable. Since no such instruction was ever recorded for the second, continuation-declared name, the lookup falls through and resolves to `undefined` → `""`, which gets substituted into the `FROM` line, producing `image:` with no tag.
## Workaround
Declare each `ARG` on its own line instead of combining them into one instruction via `\` continuation:
```dockerfile
ARG VERSION_UNUSED=x
ARG VERSION_BASE=latest
```
This is recognized correctly by both real Docker/BuildKit and the extension's scanner.
## Suggested fix
The Dockerfile-instruction regex (and/or the code that walks continuation lines) should join `\`-continued lines before matching, the same way BuildKit's own parser does — or, at minimum, should recognize additional `name[=value]` pairs appearing on continuation lines of an `ARG`/`ENV` instruction.
Contributor guide
Assessment
This issue has not been assessed yet.