Skip to content

Commit 65459c9

Browse files
committed
Add devcontainer support for Codespaces, local, and Claude Code
Adds .devcontainer/devcontainer.json (docker-compose based, reusing the existing dev target/services) with: - git, github-cli, and common-utils features for a usable shell/terminal - the official Claude Code devcontainer feature, with CLAUDE_CONFIG_DIR pointed at a persisted volume so login survives rebuilds - docker-outside-of-docker so `docker compose logs` works from the integrated terminal (requires moby:false, since moby packages aren't published for Debian trixie) - forwarded ports for the app, Sentry Spotlight, and Storybook Removes the old root-level devcontainer.json, which pointed at a nonexistent workspace path and predates the .devcontainer/ convention. Requires the Dockerfile syntax bump to 1.4: devcontainer feature injection uses BuildKit named build contexts, which aren't supported under 1.2. Also bakes Playwright's browsers into the dev image (otherwise lost every time the devcontainer is recreated) so `npm run test:e2e` and the Storybook vitest addon work out of the box, pins the compose port mappings to 0.0.0.0 (Docker's default dual-stack publish was confusing VS Code's port forwarding into offering a non-working [::1] address), and allow-lists localhost/127.0.0.1 as Next.js dev origins for the same forwarded-port reason. Fixes an unrelated Storybook glob that never matched any files (all stories are .tsx; the plain .[jt]s pattern only matches .js/.ts) and was printing a spurious "no story files found" warning on every start.
1 parent 6a50ea3 commit 65459c9

8 files changed

Lines changed: 96 additions & 9 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"features": {
3+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {
4+
"version": "1.0.5",
5+
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
6+
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
7+
},
8+
"ghcr.io/devcontainers/features/common-utils:2": {
9+
"version": "2.5.9",
10+
"resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a",
11+
"integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a"
12+
},
13+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
14+
"version": "1.10.0",
15+
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e",
16+
"integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e"
17+
},
18+
"ghcr.io/devcontainers/features/git:1": {
19+
"version": "1.3.8",
20+
"resolved": "ghcr.io/devcontainers/features/git@sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2",
21+
"integrity": "sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2"
22+
},
23+
"ghcr.io/devcontainers/features/github-cli:1": {
24+
"version": "1.1.0",
25+
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
26+
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
27+
}
28+
}
29+
}

.devcontainer/devcontainer.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "Mariner's Dashboard",
3+
"dockerComposeFile": ["../docker-compose.yaml", "../docker-compose.dev.yaml"],
4+
"service": "client",
5+
"runServices": ["client", "spotlight"],
6+
"workspaceFolder": "/app",
7+
"overrideCommand": false,
8+
"shutdownAction": "stopCompose",
9+
"features": {
10+
"ghcr.io/devcontainers/features/git:1": {},
11+
"ghcr.io/devcontainers/features/github-cli:1": {},
12+
"ghcr.io/devcontainers/features/common-utils:2": {
13+
"installOhMyZsh": true,
14+
"configureZshAsDefaultShell": true
15+
},
16+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
17+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
18+
"moby": false
19+
}
20+
},
21+
"forwardPorts": [3000, 8969, 6006],
22+
"portsAttributes": {
23+
"3000": {
24+
"label": "Next.js app",
25+
"onAutoForward": "openBrowser"
26+
},
27+
"8969": {
28+
"label": "Sentry Spotlight",
29+
"onAutoForward": "silent"
30+
},
31+
"6006": {
32+
"label": "Storybook",
33+
"onAutoForward": "silent"
34+
}
35+
},
36+
"mounts": [
37+
"source=neracoos-buoy-app-claude-config,target=/root/.claude,type=volume",
38+
"source=neracoos-buoy-app-gh-config,target=/root/.config/gh,type=volume"
39+
],
40+
"containerEnv": {
41+
"CLAUDE_CONFIG_DIR": "/root/.claude"
42+
},
43+
"postCreateCommand": "git config --global --add safe.directory /app",
44+
"customizations": {
45+
"vscode": {
46+
"settings": {
47+
"git.enabled": true,
48+
"scm.diffDecorations": "all"
49+
},
50+
"extensions": ["vitest.explorer", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "editorconfig.editorconfig"]
51+
}
52+
}
53+
}

.storybook/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { StorybookConfig } from "@storybook/nextjs-vite"
22

33
const config: StorybookConfig = {
4-
stories: ["../src/**/*.stories.[jt]s", "../src/**/*.stories.[jt]sx", "../app/**/*.stories.[jt]sx"],
4+
stories: ["../src/**/*.stories.[jt]sx", "../app/**/*.stories.[jt]sx"],
55

66
addons: ["@storybook/addon-a11y", "@storybook/addon-links", "@storybook/addon-docs", "@storybook/addon-vitest"],
77

Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#syntax=docker/dockerfile:1.2
1+
#syntax=docker/dockerfile:1.4
22
FROM node:26.5.0-slim@sha256:715e55e4b84e4bb0ff48e49b398a848f08e55daed8eb6a0ea1839ae53bc57583 AS base
33

44
# Install dependencies only when needed
@@ -17,6 +17,11 @@ WORKDIR /app
1717
COPY --from=deps /app/node_modules ./node_modules
1818
COPY . .
1919

20+
# Playwright (e2e tests) and the Storybook vitest addon both need real browsers,
21+
# which aren't part of node_modules. Install them here so they're baked into the
22+
# dev image rather than lost every time the devcontainer is recreated.
23+
RUN npx playwright install --with-deps chromium firefox
24+
2025
# Rebuild the source code only when needed
2126
FROM base AS builder
2227

devcontainer.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

docker-compose.dev.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ services:
88
target: dev
99
command: npm run dev
1010
stdin_open: true
11+
ports:
12+
- "0.0.0.0:6006:6006"
1113
volumes:
1214
- .:/app:cached
1315
- /app/node_modules/
@@ -24,4 +26,4 @@ services:
2426
spotlight:
2527
image: ghcr.io/getsentry/spotlight:latest
2628
ports:
27-
- "8969:8969"
29+
- "0.0.0.0:8969:8969"

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
# command: "npm run dev"
88
stdin_open: true
99
ports:
10-
- "3000:3000"
10+
- "0.0.0.0:3000:3000"
1111
environment:
1212
NEXT_PUBLIC_ERDDAP_SERVICE: https://buoybarn.neracoos.org
1313
# NEXT_PUBLIC_ERDDAP_SERVICE: http://localhost:8080

next.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const nextConfig = {
1111
silenceDeprecations: ["color-functions", "global-builtin", "import", "if-function"],
1212
},
1313
serverExternalPackages: ["web-worker"],
14+
// Devcontainer/Docker forward ports through 127.0.0.1 and localhost rather than
15+
// the container's own hostname, which Next.js's dev server otherwise blocks as cross-origin.
16+
allowedDevOrigins: ["localhost", "127.0.0.1"],
1417
experimental: {
1518
craCompat: false,
1619
},

0 commit comments

Comments
 (0)