Exposing a Node.js App Over 80/443 on a Subdomain in DDEV
Have you ever needed to run a separate Node.js application alongside your main site in DDEV, and serve it securely
over ports 80 and 443 on a custom subdomain? While DDEV has built-in mechanisms for exposing additional ports,
sometimes you need more control, especially if you want to expos it through a domain like
frontend.example.ddev.site instead of a port-specific URL. This is where Traefik, bundled with DDEV,
becomes incredibly powerful.
In this post, we’ll walk through how to configure DDEV and Traefik to proxy requests from a subdomain directly
to your Node.js app on port 3000 within the DDEV web container.
Why Not Just Use web_extra_exposed_ports?
DDEV's' web_extra_exposed_ports feature is great for making your service accessible via a specific port
(e.g., (3000)). However, it doesn’t magically set up a subdomain for you to use on standard web ports (80/443).
If you want frontend.example.ddev.site to map to your Node.js app over HTTPS, you need a reverse proxy rule.
That’s where Traefik comes in.
Step 1: Update Your .ddev/config.yaml
In your project’s .ddev/config.yaml, define the project name and the additional hostname you want to use. For example:
name: example
additional_hostnames:
- frontend.example
(Optional) You can still use web_extra_exposed_ports to expose the Node.js port if you want:
web_extra_exposed_ports:
- name: node-app
container_port: 3000
http_port: 3000
https_port: 3001
However, for a subdomain over standard web ports, the critical part is the next step with Traefik.
Step 2: Create a Project-level Traefik Configuration File
In your project's .ddev/traefik/config folder add a file named frontend.yaml. In frontend.yaml, you’ll define two routers—one for HTTP (port 80) and one for HTTPS (port 443)—and
a service that points to the Node.js app on port 3000.
http:
routers:
# Router for HTTP (port 80)
example-web-80-http-frontend:
entrypoints:
- http-80
rule: Host(`frontend.example.ddev.site`)
service: "example-web-3000"
ruleSyntax: v3
tls: false
priority: 100
# Router for HTTPS (port 443)
example-web-80-https-frontend:
entrypoints:
- http-443
rule: Host(`frontend.example.ddev.site`)
service: "example-web-3000"
ruleSyntax: v3
tls: true
priority: 100
services:
# The custom service that routes to your Node app
example-web-3000:
loadbalancer:
servers:
- url: http://ddev-example-web:3000
Here’s what’s happening:
- Routers: Each router inspects incoming requests. If the hostname matches frontend.example.ddev.site, it passes the request to the example-web-3000 service.
- Service: Defines where to actually send the traffic. In this case, http://ddev-example-web:3000 is the internal address of the web container running on port 3000.
Step 3: Restart DDEV
Run:
ddev restart
DDEV will pick up your new Traefik configuration, and you should now be able to access your Node.js application at:
ddev launch https://frontend.example.ddev.site
No more messing with non-standard port numbers in your URLs!
Wrapping Up
By leveraging Traefik's routing capabilities, you can expose any service running in the web container on standard HTTP/HTTPS ports and map it to a dedicated subdomain. This approach keeps your development environment clean, user-friendly, and closer to production-like URLs.
If you’ve followed these steps, your Node.js application will be served seamlessly over frontend.example.ddev.site.
Further Reading
Do You Have a Favorite DDEV Recipe? Contribute It!
We welcome community contributions to the DDEV blog and would love to have yours. The ddev.com repository has full details, and there's even a training session on how to do it. It's all just Markdown and we'll help!