Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
"name": "Ubuntu",
"name": "Debian Trixie",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:noble",
"image": "mcr.microsoft.com/devcontainers/base:trixie",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:4": {},
"ghcr.io/pablozaiden/devcontainers-features/bun": {}
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It does not modify the original `devcontainer.json`. Instead, it generates a der

## What it does

- Discovers `.devcontainer/devcontainer.json` or `.devcontainer.json` in the current directory, can target `.devcontainer/<subpath>/devcontainer.json` with a flag, and falls back to the built-in `ubuntu` template when no repo devcontainer is present.
- Discovers `.devcontainer/devcontainer.json` or `.devcontainer.json` in the current directory, can target `.devcontainer/<subpath>/devcontainer.json` with a flag, and falls back to the built-in Debian Trixie template when no repo devcontainer is present.
- Reuses or creates the devcontainer with Docker + Dev Container CLI.
- Names the managed container as `devbox-<project>-<port>`.
- Publishes the same TCP port on host and container.
Expand Down Expand Up @@ -109,9 +109,9 @@ When you run `devbox up`, the port precedence is:

When you run `devbox rebuild`, omitting the port reuses the last stored port for the current workspace.

If no repo devcontainer is found and no previous template source is stored, `devbox up` automatically starts from the built-in `ubuntu` template. `devbox rebuild <port>` does the same when there is enough information to create the devbox but no prior workspace state exists. Devbox prints a message when this automatic fallback is used.
If no repo devcontainer is found and no previous template source is stored, `devbox up` automatically starts from the built-in Debian Trixie template. `devbox rebuild <port>` does the same when there is enough information to create the devbox but no prior workspace state exists. Devbox prints a message when this automatic fallback is used.

`devbox rebuild` reuses the previously selected source for the workspace. If the workspace was started from `--template` or the automatic Ubuntu fallback, rebuild uses that saved template again. `rebuild --template ...` is intentionally not supported.
`devbox rebuild` reuses the previously selected source for the workspace. If the workspace was started from `--template` or the automatic Debian Trixie fallback, rebuild uses that saved template again. `rebuild --template ...` is intentionally not supported.

GitHub CLI authentication for `GH_TOKEN` injection can be pinned per workspace with `--gh-user <login>` and optional `--gh-host <host>`. Devbox stores only the selected account metadata in `.devbox/state.json` as `githubAuth`; it does not store the token. Later `up`, `rebuild`, and `arise` runs reuse that account by calling `gh auth token --hostname <host> --user <login>`. The selection precedence is: explicit flags, `DEVBOX_GH_USER` / `DEVBOX_GH_HOST`, saved `.devbox/state.json`, then the currently active `gh` account.

Expand All @@ -123,7 +123,7 @@ GitHub CLI authentication for `GH_TOKEN` injection can be pinned per workspace w

Built-in templates:

- `ubuntu`
- `ubuntu` (Debian Trixie base)
- `dotnet`
- `typescript`
- `python`
Expand Down Expand Up @@ -216,7 +216,7 @@ The complex example uses several devcontainer features, so the first `up` or `re
- `.devbox/` contains all devbox-owned local state (`state.json`, `user-data/`, template generated configs, and `ssh/`) and should stay ignored by version control.
- `.devbox/state.json` may include `githubAuth: { "host": "...", "user": "..." }` so tools can detect or preserve the GitHub CLI account devbox will use for future `GH_TOKEN` injection.
- `--devcontainer-subpath services/api` tells `devbox` to use `.devcontainer/services/api/devcontainer.json`.
- `--template <name>` explicitly chooses a built-in template, even if the repo already has a devcontainer definition. If no repo devcontainer exists and no template was previously saved, omitting `--template` falls back to `ubuntu`.
- `--template <name>` explicitly chooses a built-in template, even if the repo already has a devcontainer definition. If no repo devcontainer exists and no template was previously saved, omitting `--template` falls back to the `ubuntu` template identifier, which uses the Debian Trixie base.
- `--gh-user <login>` and `--gh-host <host>` select the GitHub CLI account used for `GH_TOKEN` injection without changing the globally active `gh` account.
- `devbox shell` opens an interactive shell inside the running managed container for the current workspace.
- `devbox status` reports live container state when available and falls back to saved workspace state in `.devbox/state.json` plus the persisted `.devbox/ssh/credentials` password file and `.devbox/ssh/metadata.json` metadata when the container is stopped or Docker is unavailable.
Expand Down
2 changes: 1 addition & 1 deletion examples/complex-workspace/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Feature-heavy sample workspace used to exercise slower first-time container setup.
{
"name": "devbox-complex-workspace",
"image": "mcr.microsoft.com/devcontainers/base:noble",
"image": "mcr.microsoft.com/devcontainers/base:trixie",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:4": {},
Expand Down
2 changes: 1 addition & 1 deletion examples/smoke-workspace/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "devbox-smoke-workspace",
"image": "mcr.microsoft.com/devcontainers/base:noble",
"image": "mcr.microsoft.com/devcontainers/base:trixie",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:4": {}
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async function handleUpLike(
preferStateSource: command === "rebuild",
});
if (resolvedConfig.templateSelection === "fallback") {
console.log("No devcontainer definition found; using built-in ubuntu template.");
console.log("No devcontainer definition found; using built-in Debian Trixie template.");
}
Comment on lines 182 to 184
const generatedConfigPath = resolvedConfig.generatedConfigPath;
const userDataDir = getWorkspaceUserDataDir(workspacePath);
Expand Down
4 changes: 2 additions & 2 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export function helpText(): string {
` ${CLI_NAME} --help`,
"",
"Commands:",
" up Start or reuse the managed devcontainer; falls back to the ubuntu template when none is found.",
" rebuild Recreate the managed devcontainer; falls back to the ubuntu template when no repo devcontainer or prior state exists.",
" up Start or reuse the managed devcontainer; falls back to the built-in Debian Trixie template when none is found.",
" rebuild Recreate the managed devcontainer; falls back to the built-in Debian Trixie template when no repo devcontainer or prior state exists.",
Comment on lines +159 to +160
" shell Open an interactive shell in the running managed container.",
" status Print JSON describing the managed devbox for this workspace.",
" templates Print JSON describing the built-in templates.",
Expand Down
20 changes: 10 additions & 10 deletions src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export interface DevboxTemplateSummary {
runnerCompatible: boolean;
}

const BASE_IMAGE = "mcr.microsoft.com/devcontainers/base:noble";
const BASE_NAME = "noble";
const BASE_IMAGE = "mcr.microsoft.com/devcontainers/base:trixie";
const BASE_NAME = "trixie";
const DOCKER_IN_DOCKER_FEATURE = "ghcr.io/devcontainers/features/docker-in-docker:4";
const DOTNET_FEATURE = "ghcr.io/devcontainers/features/dotnet:2";
const GO_FEATURE = "ghcr.io/devcontainers/features/go:1";
Expand All @@ -40,48 +40,48 @@ const UV_FEATURE = "ghcr.io/devcontainers-extra/features/uv:1";
const TEMPLATE_DEFINITIONS: Record<string, DevboxTemplateDefinition> = {
ubuntu: createTemplateDefinition({
name: "ubuntu",
description: "Ubuntu noble base image with Docker-in-Docker preinstalled.",
runtimeVersion: "Ubuntu noble",
description: "Debian Trixie base image with Docker-in-Docker preinstalled.",
runtimeVersion: "Debian Trixie",
languages: [],
}),
dotnet: createTemplateDefinition({
name: "dotnet",
description: ".NET SDK on Ubuntu noble via the official devcontainer feature.",
description: ".NET SDK on Debian Trixie via the official devcontainer feature.",
runtimeVersion: ".NET SDK",
languages: ["dotnet", "csharp", "fsharp"],
features: [DOTNET_FEATURE],
}),
typescript: createTemplateDefinition({
name: "typescript",
description: "Node.js and Bun on Ubuntu noble via devcontainer features.",
description: "Node.js and Bun on Debian Trixie via devcontainer features.",
runtimeVersion: "Node.js + Bun",
languages: ["node", "bun", "typescript", "javascript"],
features: [NODE_FEATURE, BUN_FEATURE],
}),
python: createTemplateDefinition({
name: "python",
description: "Python workflows on Ubuntu noble via the uv feature.",
description: "Python workflows on Debian Trixie via the uv feature.",
runtimeVersion: "Python via uv",
languages: ["python"],
features: [UV_FEATURE],
}),
go: createTemplateDefinition({
name: "go",
description: "Go on Ubuntu noble via the official devcontainer feature.",
description: "Go on Debian Trixie via the official devcontainer feature.",
runtimeVersion: "Go",
languages: ["go"],
features: [GO_FEATURE],
}),
rust: createTemplateDefinition({
name: "rust",
description: "Rust on Ubuntu noble via the official devcontainer feature.",
description: "Rust on Debian Trixie via the official devcontainer feature.",
runtimeVersion: "Rust",
languages: ["rust"],
features: [RUST_FEATURE],
}),
java: createTemplateDefinition({
name: "java",
description: "Java on Ubuntu noble via the official devcontainer feature.",
description: "Java on Debian Trixie via the official devcontainer feature.",
runtimeVersion: "Java",
languages: ["java"],
features: [JAVA_FEATURE],
Expand Down
8 changes: 4 additions & 4 deletions tests/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ describe("helpText", () => {
expect(text).toContain("help");
});

test("mentions ubuntu fallback for up and rebuild", () => {
test("mentions the Debian Trixie fallback for up and rebuild", () => {
const text = helpText();
expect(text).toContain(
"\n up Start or reuse the managed devcontainer; falls back to the ubuntu template when none is found.",
"\n up Start or reuse the managed devcontainer; falls back to the built-in Debian Trixie template when none is found.",
);
expect(text).toContain(
"\n rebuild Recreate the managed devcontainer; falls back to the ubuntu template when no repo devcontainer or prior state exists.",
"\n rebuild Recreate the managed devcontainer; falls back to the built-in Debian Trixie template when no repo devcontainer or prior state exists.",
);
});
});
Expand Down Expand Up @@ -574,7 +574,7 @@ describe("resolveWorkspaceConfig", () => {
expect(rebuildStyleResolution.template?.name).toBe("python");
expect(rebuildStyleResolution.templateSelection).toBe("state");
expect(rebuildStyleResolution.config).toEqual({
image: "mcr.microsoft.com/devcontainers/base:noble",
image: "mcr.microsoft.com/devcontainers/base:trixie",
features: {
"ghcr.io/devcontainers/features/docker-in-docker:4": {},
"ghcr.io/devcontainers-extra/features/uv:1": {},
Expand Down
4 changes: 2 additions & 2 deletions tests/examples.live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@

describe("example workspaces (real devcontainers)", () => {
liveTest(
"template workspace falls back to the ubuntu template without a repo devcontainer",
"template workspace falls back to the Debian Trixie template without a repo devcontainer",
async () => {
const fixture = await setupLiveFixture("template-workspace");
const up = runCli(fixture, ["up", "--allow-missing-ssh"]);

expect(up.exitCode).toBe(0);
expect(up.stdout).toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(up.stdout).toContain("No devcontainer definition found; using built-in Debian Trixie template.");
expect(up.stdout).toContain("Devcontainer is ready");
expect(up.stdout).toContain("SSH server:");
expect(up.stdout).toContain("Ready.");
Expand Down Expand Up @@ -535,7 +535,7 @@
};

if (output.exitCode !== 0 && !options?.allowFailure) {
throw new Error(

Check failure on line 538 in tests/examples.live.test.ts

View workflow job for this annotation

GitHub Actions / Test and build

error: Command failed: /home/runner/.bun/bin/bun run /home/runner/work/devbox/devbox/src/cli.ts up 42569 --allow-missing-ssh

Using port 42569. Reuse the previous workspace port when available, otherwise auto-assign the first free port starting at 5001. Starting workspace on port 42569... Preparing devcontainer. First builds with features may take several minutes... stderr: Warning: No usable SSH agent socket was found. Set SSH_AUTH_SOCK or use Docker Desktop host services. Continuing without SSH agent sharing. Warning: Host known_hosts was not found and was skipped: /tmp/devbox-live-example-2hswPw/home/.ssh/known_hosts. Preparing devcontainer... > input: mcr.microsoft.com/devcontainers/base:trixie > > resource: mcr.microsoft.com/devcontainers/base > id: base > owner: devcontainers > namespace: devcontainers > registry: mcr.microsoft.com > path: devcontainers/base > > version: trixie > tag?: trixie > digest?: undefined manifest url: https://mcr.microsoft.com/v2/devcontainers/base/manifests/trixie [httpOci] 404 (NoAuth): https://mcr.microsoft.com/v2/devcontainers/base/manifests/trixie Did not fetch target with expected mimetype 'application/vnd.docker.distribution.manifest.v2+json': {"errors":[{"code":"MANIFEST_UNKNOWN","message":"OCI image index found, but accept header does not support OCI image index"}]} [httpOci] 200 (NoAuth): https://mcr.microsoft.com/v2/devcontainers/base/manifests/trixie Fetched: { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:8337830a615a5bad80dea7a5e7b810f5af8167ffc9882ddfc1beddab3ba63ada", "size": 2008, "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:fc573a7fb8c54a39f5ee5c6f53784b83adbf8cbf2508ad439a3c3d0bfbd025d4", "size": 2008, "platform": { "architecture": "arm64", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:64df59df9c7843f08a5881ff7cf2a0a8bb3a1cdc673d8412a03b88f22d1afd06", "size": 838, "annotations": { "vnd.docker.reference.digest": "sha256:8337830a615a5bad80dea7a5e7b810f5af8167ffc9882ddfc1beddab3ba63ada", "vnd.docker.reference.type": "attestation-manifest" }, "platform": { "architecture": "unknown", "os": "unknown" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:19de9c09fa6090bd913088c5172d4022d279baf0af214e76411760a9092623e4", "size": 838, "annotations": { "vnd.docker.reference.digest": "sha256:fc573a7fb8c54a39f5ee5c6f53784b83adbf8cbf2508ad439a3c3d0bfbd025d4", "vnd.docker.reference.type": "attestation-manifest" }, "platform": { "architecture": "unknown", "os": "unknown" } } ] } [httpOci] 200 (NoAuth): https://mcr.microsoft.com/v2/devcontainers/base/manifests/sha256:8337830a615a5bad80dea7a5e7b810f5af8167ffc9882ddfc1beddab3ba63ada blob url: https://mcr.microsoft.com/v2/devcontainers/base/blobs/sha256:e354ee751b57912e7c590155b23aba9e21f184f30138ea17d75363d77ad588e8 Workspace: /tmp/devbox-live-example-2hswPw/smoke-workspace > > namespace: devcontainers/features > registry: ghcr.io > path: devcontainers/features/docker-in-docker > > version: 4 > tag?: 4 > digest?: undefined manifest url: https://ghcr.io/v2/devcontainers/features/docker-in-docker/manifests/4 [httpOci] Attempting to authenticate via 'Bearer' auth. [httpOci] Environment DOCKER_CONFIG is set to '/home/runner/.docker' [httpOci] No authentication credentials found for registry 'ghcr.io' via docker config or credential helper. [httpOci] No authentication credentials found for registry 'ghcr.io'. Accessi
`Command failed: ${command.join(" ")}\nstdout:\n${output.stdout || "<empty>"}\nstderr:\n${output.stderr || "<empty>"}`,
);
}
Expand Down
26 changes: 13 additions & 13 deletions tests/examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ describe("example workspaces (simulated host tools)", () => {
expect(await readFile(String(fixture.sourceConfigPath), "utf8")).toBe(sourceBefore);

const generatedConfig = await readJson(fixture.generatedConfigPath);
expect(generatedConfig.image).toBe("mcr.microsoft.com/devcontainers/base:noble");
expect(generatedConfig.image).toBe("mcr.microsoft.com/devcontainers/base:trixie");
expect(generatedConfig.features).toEqual({
"ghcr.io/devcontainers/features/docker-in-docker:4": {},
});
Expand Down Expand Up @@ -527,7 +527,7 @@ describe("example workspaces (simulated host tools)", () => {
expect(commandsAfterDown.filter((entry) => entry.tool === "docker" && entry.args[0] === "rm").length).toBeGreaterThanOrEqual(2);
});

test("template-backed workspace lists templates, falls back to ubuntu, and rebuilds from saved state", async () => {
test("template-backed workspace lists templates, falls back to the default template, and rebuilds from saved state", async () => {
const fixture = await setupExampleFixture("template-workspace");

const templates = runCli(fixture, ["templates"]);
Expand All @@ -539,11 +539,11 @@ describe("example workspaces (simulated host tools)", () => {
const up = runCli(fixture, ["up", "--allow-missing-ssh"]);
expect(up.exitCode).toBe(0);
expect(up.stdout).toContain("Using port 5001.");
expect(up.stdout).toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(up.stdout).toContain("No devcontainer definition found; using built-in Debian Trixie template.");
expect(existsSync(fixture.generatedConfigPath)).toBe(true);

const generatedConfig = await readJson(fixture.generatedConfigPath);
expect(generatedConfig.image).toBe("mcr.microsoft.com/devcontainers/base:noble");
expect(generatedConfig.image).toBe("mcr.microsoft.com/devcontainers/base:trixie");
expect(generatedConfig.features).toEqual({
"ghcr.io/devcontainers/features/docker-in-docker:4": {},
});
Expand All @@ -553,11 +553,11 @@ describe("example workspaces (simulated host tools)", () => {
expect(state.configSource).toBe("template");
expect(state.sourceConfigPath).toBeNull();
expect(state.template.name).toBe("ubuntu");
expect(state.template.image).toBe("mcr.microsoft.com/devcontainers/base:noble");
expect(state.template.image).toBe("mcr.microsoft.com/devcontainers/base:trixie");
expect(state.template.pinnedReference).toBe(
"mcr.microsoft.com/devcontainers/base:noble + ghcr.io/devcontainers/features/docker-in-docker:4",
"mcr.microsoft.com/devcontainers/base:trixie + ghcr.io/devcontainers/features/docker-in-docker:4",
);
expect(state.template.runtimeVersion).toBe("Ubuntu noble");
expect(state.template.runtimeVersion).toBe("Debian Trixie");

const statusWhileRunning = runCli(fixture, ["status"]);
expect(statusWhileRunning.exitCode).toBe(0);
Expand All @@ -568,7 +568,7 @@ describe("example workspaces (simulated host tools)", () => {
const rebuild = runCli(fixture, ["rebuild", "--allow-missing-ssh"]);
expect(rebuild.exitCode).toBe(0);
expect(rebuild.stdout).toContain("Using port 5001.");
expect(rebuild.stdout).not.toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(rebuild.stdout).not.toContain("No devcontainer definition found; using built-in Debian Trixie template.");
expect(rebuild.stdout).toContain("Ready.");

const rebuildWithTemplate = runCli(fixture, ["rebuild", "--template", "python"]);
Expand All @@ -588,13 +588,13 @@ describe("example workspaces (simulated host tools)", () => {
expect(stoppedStatus.templateName).toBe("ubuntu");
});

test("rebuild without prior state falls back to ubuntu when a port is provided", async () => {
test("rebuild without prior state falls back to the default template when a port is provided", async () => {
const fixture = await setupExampleFixture("template-workspace");

const rebuild = runCli(fixture, ["rebuild", "5010", "--allow-missing-ssh"]);
expect(rebuild.exitCode).toBe(0);
expect(rebuild.stdout).toContain("Using port 5010.");
expect(rebuild.stdout).toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(rebuild.stdout).toContain("No devcontainer definition found; using built-in Debian Trixie template.");
expect(existsSync(fixture.generatedConfigPath)).toBe(true);

const state = await readJson(fixture.statePath);
Expand All @@ -604,19 +604,19 @@ describe("example workspaces (simulated host tools)", () => {
expect(state.template.name).toBe("ubuntu");
});

test("workspace without a repo devcontainer reuses a saved non-ubuntu template", async () => {
test("workspace without a repo devcontainer reuses a saved non-default template", async () => {
const fixture = await setupExampleFixture("template-workspace");

const explicitTemplateUp = runCli(fixture, ["up", "--template", "python", "--allow-missing-ssh"]);
expect(explicitTemplateUp.exitCode).toBe(0);
expect(explicitTemplateUp.stdout).not.toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(explicitTemplateUp.stdout).not.toContain("No devcontainer definition found; using built-in Debian Trixie template.");

const down = runCli(fixture, ["down"]);
expect(down.exitCode).toBe(0);

const upFromState = runCli(fixture, ["up", "--allow-missing-ssh"]);
expect(upFromState.exitCode).toBe(0);
expect(upFromState.stdout).not.toContain("No devcontainer definition found; using built-in ubuntu template.");
expect(upFromState.stdout).not.toContain("No devcontainer definition found; using built-in Debian Trixie template.");

const state = await readJson(fixture.statePath);
expect(state.configSource).toBe("template");
Expand Down
Loading
Loading