Stockio

Host Multiple Node.js Apps on One Server With Docker

Deploy two Node.js apps with Docker Compose, isolated networks, and an NGINX proxy, then plan HTTPS, resource limits, backups, and safe production updates.

By Ethan Clarke

Performance & SEO Specialist ·

On this page

Two Node.js applications can share a server without sharing dependencies, release schedules, or public ports. Containers provide packaging and process isolation, while a reverse proxy gives each application its own hostname.

This guide builds a small Docker Compose deployment with separate application networks, persistent-data planning, and a controlled update workflow. The example assumes an Ubuntu-style server with Docker Engine and the Compose plugin already installed.

Plan addresses and exposure first

Before writing Compose files, list your server's LAN, VPN, and existing container ranges. Overlapping subnets can route traffic to the wrong interface and make otherwise healthy services appear unreachable.

Use the Subnet & CIDR Calculator below to check whether an intended container range overlaps addresses you already use. Enter 172.28.10.0/24, inspect its boundaries, and test representative addresses from your other networks. The calculator checks membership; compare every existing CIDR's boundaries rather than assuming one sample proves two ranges never overlap.

This example reserves 172.28.10.0/24 and 172.28.11.0/24 for two application networks. They are private address space, not universally safe defaults. Replace them when your own inventory shows a conflict. RFC 1918 defines private IPv4 ranges, but private does not mean unique across organizations or VPNs.

Only the reverse proxy needs published web ports. Databases, caches, and application listeners should remain unpublished unless there is a specific access requirement.

Package an application for predictable startup

Build an image from a known Node.js release compatible with your application. Keep dependency installation reproducible with a committed lockfile. This Dockerfile assumes a JavaScript app with server.js, a startable HTTP listener, and no build step; TypeScript or bundled projects need a separate build stage.

FROM node:24-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --chown=node:node . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]

Add node_modules, .git, .env, local credentials, and unnecessary uploads to a .dockerignore file before building. Otherwise the final copy can overwrite image dependencies or embed secrets. For production release tracking, pin an approved image digest and update it intentionally when applying security fixes.

Your application must listen on 0.0.0.0:3000 inside the container, not just its own loopback interface. Handle SIGTERM by stopping new requests and closing database connections. Writing logs to standard output keeps them available through Docker's logging system rather than hidden inside an ephemeral filesystem.

Avoid baking configuration secrets into image layers. Runtime-mounted secrets or a dedicated secret manager are preferable, and applications must explicitly read the selected secret source. An environment variable is configuration delivery, not encryption.

Connect two applications through one proxy

Create two application directories, app-a and app-b, containing their Dockerfiles and source. Place the following compose.yaml beside them. Separate networks prevent the two applications from directly discovering one another; the proxy intentionally joins both networks.

services:
app_a:
  build: ./app-a
  init: true
  restart: unless-stopped
  mem_limit: 384m
  cpus: 0.75
  networks: [a_net]
app_b:
  build: ./app-b
  init: true
  restart: unless-stopped
  mem_limit: 384m
  cpus: 0.75
  networks: [b_net]
proxy:
  image: nginx:stable
  restart: unless-stopped
  ports:
    - "80:80"
  volumes:
    - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
  networks: [a_net, b_net]
networks:
a_net:
  ipam:
    config:
      - subnet: 172.28.10.0/24
b_net:
  ipam:
    config:
      - subnet: 172.28.11.0/24

These resource ceilings are illustrative starting points, not capacity recommendations. Measure resident memory, CPU saturation, and response times before adjusting them. Leave room for the OS, proxy, filesystem cache, and deployment builds. A hard memory limit can terminate a process instead of slowing it gracefully.

Compose creates user-defined bridge networks with outbound connectivity by default; these are not air-gapped networks. Review the Docker documentation for network behavior and published-port firewall interactions. Do not assume a familiar host firewall rule automatically blocks every Docker-published port.

Route hostnames and add HTTPS deliberately

Save this as nginx.conf. The proxy reaches each application by its Compose service name, so fixed container IPs are unnecessary. Replace the hostnames with names you control and point their DNS records to the server.

server {
  listen 80;
  server_name a.example.com;
  location / {
      proxy_pass http://app_a:3000;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
  }
}
server {
  listen 80;
  server_name b.example.com;
  location / {
      proxy_pass http://app_b:3000;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
  }
}

This is an HTTP-only rehearsal, not a complete public deployment. Before accepting credentials or personal data, add certificate issuance, persistent certificate storage, port 443, and HTTPS server blocks using a maintained proxy workflow. The NGINX documentation explains proxy directives and TLS configuration.

Set framework proxy-trust settings narrowly for this topology. Trusting arbitrary forwarded headers from any source can corrupt client-IP detection or HTTPS redirects. If a CDN or additional load balancer sits in front, explicitly revise the trusted-proxy chain rather than retaining assumptions from the single-proxy example.

For certificate planning, the wildcard SSL automation guide covers DNS-based issuance. WebSocket applications additionally need appropriate upgrade handling; the basic HTTP configuration above does not implement it.

Deploy, inspect, and roll back safely

Validate the file with docker compose config, then run docker compose up -d --build. Inspect docker compose ps and docker compose logs --tail=100 before testing each hostname. Use docker compose exec proxy nginx -t to check the active proxy configuration.

Configure log rotation as well: container logs can exhaust the host filesystem even when application memory and CPU remain comfortably within their limits.

Keep persistent state outside disposable container layers. Database volumes and uploaded files need backups with tested restores; a saved application image is not a database backup. Avoid copying live database files without an engine-supported consistent-backup method.

For a controlled release, record the current image identifier, build or pull the next release, and test its health endpoint and core workflows. Plain Compose recreation can cause a short interruption and is not automatically a zero-downtime rollout. After replacing an application container, reload or recreate the proxy so its upstream name resolves to the current container address.

Watch docker stats during normal traffic and during builds. If one application repeatedly reaches its memory limit, investigate leaks, concurrency, and caching before increasing capacity. The website audit workflow helps separate infrastructure pressure from frontend problems.

Recheck network membership with the CIDR planning tool whenever adding a VPN, database network, or new environment. Rollback readiness means retaining the previous image and ensuring database migrations remain compatible; restoring an older container cannot undo an incompatible schema change safely.

Key takeaways

  • Plan nonoverlapping networks before launching containers, especially on servers connected to VPNs.
  • Publish proxy ports only, keep application dependencies reproducible, and run application processes without root privileges.
  • Add HTTPS and backups before treating the rehearsal as production infrastructure.
  • Measure resource usage, refresh proxy resolution after replacements, and retain a tested rollback path.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Web Hosting & Servers
Web Hosting & Servers

10 Best VPS Hosting Providers

Explore ten VPS providers for growing websites and applications, with notes on managed support, cloud flexibility, and the extra maintenance that comes with virtual servers.

Includes: Subnet calculatorRavi DesaiRavi Desai
Web Hosting & Servers

How to Secure an Ubuntu VPS With UFW, Fail2ban and SSH

Harden an Ubuntu VPS with tested SSH keys, carefully staged UFW rules, and a verified Fail2ban jail while preserving a reliable emergency access path.

Includes: Subnet calculatorEthan ClarkeEthan Clarke
DevOps & Deployment

10 Best Domain Registrars for Developers

Find a domain registrar that fits a developer's workflow, from DNS controls and email forwarding to privacy options, plus practical checks for renewals, transfers, and deployment.

Includes: Subnet calculatorRavi DesaiRavi Desai
VPN & Online Privacy

10 Best Free Online Privacy Check Tools

Use ten reputable free privacy checkers to review browser tracking, account exposure, connection settings, and data breaches, with practical notes on what each result can and cannot tell you.

Includes: Subnet calculatorRavi DesaiRavi Desai
Smart Home & Gadgets

How to Put Smart Devices on a Separate Wi-Fi Network

Move smart devices onto a guest or isolated network without breaking everyday controls. Learn what isolation means, how to test it, and when a VLAN makes sense.

Includes: Subnet calculatorGrace ChenGrace Chen
VPN & Online Privacy

Share a VPN Connection to Xbox or PlayStation via Ethernet

Share a supported Windows VPN connection with a console over Ethernet, avoid bridge mistakes, verify routing, and test what happens when the tunnel drops.

Includes: Subnet calculatorRavi DesaiRavi Desai