Using GitHub Codespaces Forward Ports to Access Local Services During Development
Learn how GitHub Codespaces’ forwardPorts feature lets you expose any TCP service from a devcontainer with a public URL, preserving hot‑reload and requiring no extra configuration.
12 Jun 2026, 05:25 UTC

Problem: Needing instant access to a service running inside a codespace
When you start a GitHub Codespace, the container isolates your application from the host network. If you run a web server, database, or API inside that container, you cannot reach it from your local browser unless you expose the port through some mechanism. Manually setting up SSH tunnels or ngrok adds friction and can break hot‑reload workflows.
Thesis: The built‑in forwardPorts feature in devcontainer.json gives you a zero‑configuration, public URL for any TCP service, preserving the original port number and working with hot‑reload tools.
How forward ports work
In the devcontainer.json file you declare an array called forwardPorts. Each entry is the container port you want to make reachable. When the codespace agent starts, it creates a TCP tunnel for each listed port and assigns a sub‑domain of the form https://<username>-<port>-<random>.app.github.dev. The tunnel forwards traffic directly to the container’s listening socket, so if your app binds to 0.0.0.0 or localhost the connection succeeds.
The tunnel inherits the codespace’s lifecycle: it stays alive while the codespace is running and closes when the codespace stops or times out due to inactivity. Because the original port number is preserved, tools that rely on live reload (e.g., Vite, nodemon, Webpack Dev Server) continue to function without modification.
Worked example: exposing a Node.js Express API
- Create a fresh repository and add a
.devcontainer/devcontainer.jsonfile with the following content:
{
"name": "nodejs-example",
"image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:20",
"forwardPorts": [3000],
"postCreateCommand": "npm install"
}
- Add a simple Express server in
server.js:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello from Codespace!'));
app.listen(3000, '0.0.0.0', () => {
console.log('Server listening on port 3000');
});
- Commit the files, open the repository in GitHub Codespaces (click “Code” → “Open with Codespaces”).
- When the container starts, the Codespaces dashboard shows a “Ports” tab. You should see an entry like:
- Local Address:
0.0.0.0:3000 - Public URL:
https://yourname-3000-abcd1234.app.github.dev - State: Running
Opening that URL in a browser returns “Hello from Codespace!”. If you edit server.js and save, nodemon (or any watcher you configure) restarts the server and the same URL continues to serve the updated code without needing to reconfigure the tunnel.
Trade‑offs and limitations
- Public exposure: By default anyone who knows the generated URL can reach the service. For sensitive endpoints you should add authentication (e.g., middleware that checks a token) or disable forwarding for production branches.
- Port limit: A codespace can forward at most 30 ports simultaneously. If you need more, you must consolidate services (e.g., use a reverse proxy inside the container) or remove unused forwards.
- Lifecycle coupling: The tunnel closes when the codespace stops or times out. Long‑running background work that must stay available after you close the browser will require a persistent environment (e.g., a dedicated VM) rather than relying on forwarded ports.
Actionable closing
To start using forward ports today:
- Add a
forwardPortsarray to your existingdevcontainer.jsonlisting the ports your application listens on. - Ensure the application binds to
0.0.0.0orlocalhostso the tunnel can reach it. - Verify the public URL appears in the Ports tab and test it from a browser or
curl. - If the service handles sensitive data, wrap it in an authentication layer or restrict forwarding to feature branches only.
When you hit the 30‑port limit or need longer‑lived access, consider running a sidecar proxy (like NGINX) inside the container that aggregates multiple services on a single forwarded port, or move to a self‑hosted runner for continuous availability.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.