fix(devcontainer): correct the bind and port reset (#69753)

Signed-off-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
This commit is contained in:
Mrugesh Mohapatra
2026-08-26 20:38:32 +05:30
committed by GitHub
parent 1fb1ca975f
commit c306144dd0
8 changed files with 98 additions and 97 deletions
-64
View File
@@ -1,64 +0,0 @@
# Develop freeCodeCamp in a container
The container builds itself, installs every dependency, starts MongoDB, and seeds a test user. Wait until the terminal shows `Done. Press any key to close the terminal.`
## Start the application
Choose one option:
- Open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`), type `Run Task`, then select **Start freeCodeCamp**.
- Open a terminal and run:
```bash
pnpm run develop
```
The first build takes several minutes. VS Code shows a notification when port `8000` is ready. Open that notification to view the client.
To build one part of the curriculum instead of all of it, set a filter:
```bash
FCC_SUPERBLOCK='responsive-web-design' pnpm run develop
```
## Services
| Port | Service | Notes |
| ------ | ------- | --------------------------------------- |
| `8000` | Client | The learning platform. |
| `3000` | API | API docs are at `/documentation`. |
| `8025` | Mailpit | Every email the API sends arrives here. |
### Codespaces: port 3000 must be public
The client calls the API from your browser, across two different forwarded
origins. A private port answers that call with a GitHub sign-in redirect, and
the browser reports it as a CORS error.
`post-create.sh` attempts this, but the token a codespace provides does not
carry the `codespace` scope, so the attempt usually fails. Open the **Ports**
panel, right click port `3000`, then choose **Port Visibility > Public**. Run
`gh auth login -s codespace` once if you want the script to do it for you.
A public port is reachable by anybody who has the URL, and `sample.env` sets
`FCC_ENABLE_DEV_LOGIN_MODE=true`, which serves a sign-in route that needs no
password. Stop the codespace when you finish working.
## Optional setup
For end-to-end tests:
```bash
npx playwright install chromium
```
For curriculum tests:
```bash
pnpm -F=curriculum install-puppeteer
```
## More information
For the full contribution guide, see
https://contribute.freecodecamp.org/how-to-setup-freecodecamp-locally
+4 -8
View File
@@ -24,11 +24,14 @@
// onCreateCommand: a prebuild snapshots the container after onCreateCommand
// and would freeze the wrong hostname.
"postCreateCommand": ".devcontainer/post-create.sh",
// Codespaces resets port visibility on stop, and postCreateCommand does
// not run again on a restart.
"postStartCommand": ".devcontainer/post-start.sh",
"postAttachCommand": ".devcontainer/welcome.sh",
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
"settings": {
"task.allowAutomaticTasks": "on",
"tasks": {
"version": "2.0.0",
"tasks": [
@@ -39,13 +42,6 @@
"isBackground": true,
"problemMatcher": [],
"presentation": { "reveal": "always", "panel": "dedicated" }
},
{
"label": "Open README",
"type": "shell",
"command": "code .devcontainer/README.md",
"presentation": { "reveal": "silent", "close": true },
"runOptions": { "runOn": "folderOpen" }
}
]
}
+7
View File
@@ -3,6 +3,13 @@ name: freecodecamp-devcontainer
services:
devcontainer:
image: ghcr.io/freecodecamp/devcontainer:latest
# gatsby develop defaults to host `localhost`. Where the network namespace
# has IPv6, glibc RFC 6724 precedence resolves that to ::1 first, so the
# dev server binds IPv6 loopback only and port forwarding, which connects
# over IPv4, is refused. 0.0.0.0 needs no AF_INET6 socket, so it also holds
# where IPv6 is switched off.
environment:
GATSBY_HOST: 0.0.0.0
volumes:
- ..:/workspaces/freeCodeCamp:cached
# Shares the db network namespace so MONGOHQ_URL and MAILPIT_HOST in
-24
View File
@@ -3,26 +3,6 @@ set -euo pipefail
cd "$(dirname "$0")/.."
publish_api_port() {
if gh codespace ports visibility 3000:public -c "$CODESPACE_NAME" >/dev/null 2>&1; then
printf 'Port 3000 is public. The client can reach the API.\n'
return
fi
cat <<'MSG'
Port 3000 is private, so the client cannot reach the API yet.
The token a codespace provides does not carry the "codespace" scope, so this
step usually needs you. Open the Ports panel, right click port 3000, then
choose Port Visibility, then Public.
To let this run for you next time: gh auth login -s codespace
A public port is reachable by anybody who has the URL, and the development
sign-in route needs no password. Stop the codespace when you finish working.
MSG
}
wait_for_primary() {
for _ in $(seq 1 30); do
if mongosh --quiet --eval 'if (!db.hello().isWritablePrimary) quit(1)' >/dev/null 2>&1; then
@@ -42,10 +22,6 @@ if ! wait_for_primary; then
exit 1
fi
if [[ -n "${CODESPACE_NAME:-}" ]]; then
publish_api_port
fi
set -a
# shellcheck disable=SC1091
. ./.env
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
# Runs on every start, including a resume. Codespaces resets port visibility
# when a codespace stops, and postCreateCommand does not run again.
[[ -n "${CODESPACE_NAME:-}" ]] || exit 0
for attempt in 1 2; do
status=0
err=$(timeout 20 gh codespace ports visibility 3000:public -c "$CODESPACE_NAME" 2>&1) || status=$?
if [[ $status -eq 0 ]]; then
printf 'Port 3000 is public, so the client can reach the API.\n'
printf 'Anybody with the URL can reach it, and the development sign-in\n'
printf 'route needs no password. Stop the codespace when you finish.\n'
exit 0
fi
if [[ $status -eq 124 ]]; then
err="gh timed out after 20 seconds"
fi
if [[ $status -eq 127 ]]; then
err="gh is not installed"
break
fi
if [[ $attempt -lt 2 ]]; then
sleep 5
fi
done
printf 'gh could not set the port: %s\n' "$err" >&2
cat <<'MSG'
Port 3000 is private, so the client cannot reach the API yet.
Open the Ports panel, right click port 3000, then choose Port Visibility,
then Public.
A public port is reachable by anybody who has the URL, and the development
sign-in route needs no password. Stop the codespace when you finish working.
MSG
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
cat <<'MSG'
freeCodeCamp is ready. Dependencies are installed, MongoDB is running,
and a test user is seeded.
Open a new terminal and start the development script:
pnpm run develop
Read the setup and troubleshooting guide before you begin:
https://contribute.freecodecamp.org/how-to-setup-freecodecamp-locally/
MSG
+26
View File
@@ -9,6 +9,10 @@ on:
- '.github/workflows/devcontainer-ci.yml'
- 'sample.env'
- 'package.json'
- 'turbo.json'
- 'pnpm-lock.yaml'
- 'client/package.json'
- 'client/turbo.json'
workflow_dispatch:
concurrency:
@@ -76,6 +80,28 @@ jobs:
devcontainer exec --workspace-folder . grep -q '^MAILPIT_HOST=localhost$' .env
devcontainer exec --workspace-folder . grep -q '^HOME_LOCATION=http://localhost:8000$' .env
- name: Validate the client answers on IPv4 loopback
run: |
devcontainer exec --workspace-folder . bash -c '
FCC_SUPERBLOCK=responsive-web-design pnpm run develop > /tmp/develop.log 2>&1 &
pid=$!
deadline=$((SECONDS + 2400))
while [ "$SECONDS" -lt "$deadline" ]; do
if ! kill -0 "$pid" 2>/dev/null; then
echo "pnpm run develop exited before the client answered"
cat /tmp/develop.log
exit 1
fi
if curl -sf -o /dev/null --max-time 10 http://127.0.0.1:8000/; then
echo "The client answers on 127.0.0.1:8000"
exit 0
fi
sleep 5
done
echo "The client never answered on 127.0.0.1:8000"
cat /tmp/develop.log
exit 1'
- name: Validate the Codespaces URL rewrite
run: |
devcontainer exec --workspace-folder . env \
+1 -1
View File
@@ -26,7 +26,7 @@
"outputs": ["public/**"]
},
"develop": {
"env": ["FCC_*"]
"env": ["FCC_*", "GATSBY_HOST"]
},
"setup": {
"env": [