Skip to content

Run your own app

Most tutorials assume the app and your browser are on the same computer, so they say "open http://localhost:5000." On your lab that isn't true — your browser reaches the app through the lab gateway. Three small settings make your app work here. You're changing settings, never your app's actual code.

Don't want to do this by hand?

Copy a prompt from AI prompts to fix your app and let your AI assistant make these changes for you.

The three things to set

1. Let your app accept outside connections

This is the one that trips up almost everyone. By default, lots of apps start in a private mode where only the same computer can open them. The gateway is a different machine, so it can't get in — and you see a "Nothing on port N" page. The fix: start your app on 0.0.0.0 instead of localhost.

Not sure which mode your app is in? Run this in a terminal on the lab computer (use your port number):

ss -tlnp | grep :5000
  • 0.0.0.0:5000 → good, the gateway can reach it.
  • 127.0.0.1:5000 → private mode; this is the problem. Switch it to 0.0.0.0.
  • nothing printed → the app isn't running (or it's on a different port).
The technical version (optional)

0.0.0.0 means "listen on all network interfaces"; 127.0.0.1 (localhost) is the loopback interface, reachable only from the VM itself. The reverse proxy connects from another host, so a loopback-only bind refuses its connection and nginx returns the "Nothing on port N" page.

2. Use an allowed port

Pick a port the gateway can route to — see How to open your apps. Port 5000 opens at app-lab1234…; any port from 2000 to 9000 opens at <port>-lab1234….

3. Expect a secure web address

Your app is opened over https:// and is handed its real web address (app-lab1234.labs.decoded.com), not localhost. Two things may need a nudge:

  • Some apps keep a guest list of web addresses they trust. If yours does, add app-lab1234.labs.decoded.com to it (or, while you're just developing, allow any address). Otherwise you may get a blank page or an "invalid host" message.
  • Tell your app it's being opened securely, so the links and logins it builds start with https:// and not http://. In a website's front-end code, point at your own API with plain paths like /api/..., not http://localhost:5000/....

Live-reload (the page refreshing itself as you edit) works here too. If it doesn't, it's usually the same "secure address" nudge — the recipes below set it where it's needed.

The technical version (optional)

The proxy passes your app the public hostname in the Host header, so frameworks with a host allow-list (Vite, Django, CRA, webpack) reject it until you add that host. It also sets X-Forwarded-Proto: https; trust that header (Flask ProxyFix, Express trust proxy, Django SECURE_PROXY_SSL_HEADER) so generated URLs, redirects and secure cookies use https. Dev-server hot-reload uses a WebSocket — point its client at wss://<host> on port 443 (e.g. Vite hmr.clientPort: 443, CRA WDS_SOCKET_PORT=443).


Find your tool below

Each tab shows the exact change, then the address to open. Replace lab1234 with your lab number. If a setting looks unfamiliar, that's fine — copy it as shown; it does the three things above.

Flask — bind to 0.0.0.0, and trust the proxy so url_for(..., _external=True) builds https:// links:

from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)   # (1)!
  1. Or from the CLI: flask run --host 0.0.0.0 --port 5000. Flask's dev server has no host allowlist, so nothing else is needed.

FastAPI (uvicorn) — --proxy-headers makes it honour X-Forwarded-Proto:

uvicorn main:app --host 0.0.0.0 --port 5000 \
    --proxy-headers --forwarded-allow-ips="*"

Gunicorn:

gunicorn -b 0.0.0.0:5000 --forwarded-allow-ips="*" app:app

→ Open https://app-lab1234.labs.decoded.com/ (port 5000).

runserver binds to one address; the rest is settings. The host allowlist and CSRF_TRUSTED_ORIGINS matter — without the latter, admin login and any form POST fail with a 403 over HTTPS.

# settings.py
ALLOWED_HOSTS = ["8000-lab1234.labs.decoded.com"]        # or ["*"] in dev (1)
CSRF_TRUSTED_ORIGINS = ["https://8000-lab1234.labs.decoded.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
  1. ["*"] is fine for a throwaway dev VM but never for anything you'd treat as production.
python manage.py runserver 0.0.0.0:8000

→ Open https://8000-lab1234.labs.decoded.com/. The host in the two settings above must match the URL you open — to use app-lab1234… instead, run on port 5000 and put that host there.

const app = require("express")();

app.set("trust proxy", 1);          // honour X-Forwarded-Proto / -For

app.listen(5000, "0.0.0.0", () => console.log("listening on 0.0.0.0:5000"));

Express has no host allowlist, so binding + trust proxy is enough.

→ Open https://app-lab1234.labs.decoded.com/ (port 5000).

Vite checks which web address opened it, and its live-reload needs a small setting — both are handled here:

// vite.config.js
export default {
  server: {
    host: true,                       // bind 0.0.0.0
    port: 5173,
    allowedHosts: [".labs.decoded.com"],   // (1)
    hmr: { protocol: "wss", clientPort: 443 },  // hot-reload over the proxy
  },
};
  1. The leading dot allows any *.labs.decoded.com host. allowedHosts: true also works for dev.

→ Open https://5173-lab1234.labs.decoded.com/ (or set port: 5000 and use app-lab1234…).

next dev -H 0.0.0.0 -p 3000

Next serves at the root path, so no assetPrefix is needed. If a recent Next version warns about cross-origin dev requests, add the host:

// next.config.js
module.exports = { allowedDevOrigins: ["3000-lab1234.labs.decoded.com"] };

→ Open https://3000-lab1234.labs.decoded.com/.

react-scripts checks the web address that opened it, and its live-reload uses a background connection — set both with environment variables:

HOST=0.0.0.0 \
PORT=3000 \
WDS_SOCKET_PORT=443 \
DANGEROUSLY_DISABLE_HOST_CHECK=true \
npm start

WDS_SOCKET_PORT=443 points hot-reload at the public port; DANGEROUSLY_DISABLE_HOST_CHECK is a dev-only convenience.

→ Open https://3000-lab1234.labs.decoded.com/.

Streamlit:

streamlit run app.py \
    --server.address 0.0.0.0 --server.port 8501 \
    --server.headless true \
    --server.enableCORS false --server.enableXsrfProtection false

→ https://8501-lab1234.labs.decoded.com/

Gradio:

demo.launch(server_name="0.0.0.0", server_port=7860)

→ https://7860-lab1234.labs.decoded.com/

Your own Jupyter (the platform already gives you one at nb-lab1234…):

jupyter lab --ip 0.0.0.0 --port 8888 --no-browser \
    --ServerApp.allow_origin='*' --ServerApp.trust_xheaders=True

→ https://8888-lab1234.labs.decoded.com/

Any server works as long as it binds 0.0.0.0 on a 2000–9000 port. A plain static server:

python3 -m http.server 8000 --bind 0.0.0.0

→ https://8000-lab1234.labs.decoded.com/

For anything else, the rule is the same: find its "host"/"bind"/"address" setting and set it to 0.0.0.0; find its port and make it 2000–9000; if it has a host/allowed-hosts check, add *.labs.decoded.com.


One safety note

These addresses can be opened by anyone who knows them — there's no login in front of your app unless you add one. Don't put a database, an admin panel, or anything holding secrets on an open port without protecting it. If you're not sure, ask your instructor.

Stuck on a specific symptom? See Troubleshooting.