devcontainers / devcontainers/cli

Multi-variable ARG declared via line continuation loses its second variable

Open
#1,286 0 comments 0 reactions 1 assignee Claimed by @v-Kaniska244 View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.