Skip to content

API placement: single-server vs single-swarm

Today the API runs on forge with the frontends and talks to PostgreSQL on forge-worker across the private network. The alternative is to move the API onto forge-worker so it sits next to the database, leaving forge as the web and proxy tier.

The obstacle is not networking. Both instances share the gamezmi-dev-vcn, and the Security List already allows all 10.0.0.0/16 traffic between them, so private connectivity is fully open. The obstacle is that same-origin is a rule about the public URL, not the private network. Two independent browser rules force it: with no CORS headers, the dashboard’s JavaScript cannot read a response from a different origin; and the login cookie is SameSite=Strict and path-scoped, so it is sent back only on same-site requests to the matching path. Either alone breaks a cross-origin split, and together they require the API to answer on the same origin as the dashboard. The browser reaches app.nexisapi.xyz at forge’s public IP, so whatever terminates that domain (Traefik on forge) must serve both / and /api. Moving the API to a second host means Traefik has to reach it there.

The clean way to do that is to make both instances one Docker Swarm with an overlay network, so Traefik on the manager can route to a service task wherever it runs:

Making forge and forge-worker one Docker Swarm so Traefik on forge routes /api to the API on forge-worker over an overlay network The browser hits forge's public IP over HTTPS. forge is the Swarm manager running Traefik and the static SPAs. forge-worker is a Swarm worker, private-only, running the API and PostgreSQL. Traefik routes the root path to the SPAs on forge and the /api path across the swarm overlay to the API on forge-worker. Both nodes are in the same VCN and need the swarm control-plane ports open between them. Browser app.nexisapi.xyz forge (manager) forge-worker (worker) HTTPS to public IP gamezmi-dev-vcn · private 10.0.0.0/16 forge manager · public IP forge-worker worker · private only Traefik (ingress) terminates the domains hq · admin · docs SPAs static files API (.NET) pinned via placement constraint PostgreSQL co-located with the API / localhost /api overlay Swarm control plane over the VCN: 2377 · 7946 · 4789
Traefik stays on forge and routes /api across a swarm overlay to the API on forge-worker, so the public origin never changes.

Option A - keep the API on forge (current)

Section titled “Option A - keep the API on forge (current)”

The API runs on forge next to the dashboards. Dokploy’s per-host Traefik automatically routes / and /api to containers on the same host, so there is no custom proxy configuration. The API reaches PostgreSQL on forge-worker over the private VCN, a sub-millisecond hop.

forge and forge-worker join into a single Docker Swarm (forge the manager, forge-worker a worker) with an overlay network spanning both. Traefik stays on forge and keeps terminating the public domains, but the API is pinned to forge-worker with a placement constraint and sits next to PostgreSQL. Traefik routes /api to the API task across the overlay, so same-origin is preserved with no per-request cost the browser can see.

Option A (API on forge) Option B (single swarm, API on forge-worker)
Same-origin Automatic Preserved, via the overlay
Ops complexity Low, fully Dokploy-managed Multi-node cluster; likely below Dokploy (see caveat)
API to DB hop Across the VCN (about a millisecond) localhost
Extra firewall rules None Swarm ports between nodes (below)
Failure surface One box, simple Single manager is a control-plane SPOF; overlay and node health to reason about
Scales to One shop comfortably Many tenants, HA, separating web from API/DB compute
  1. Join the swarm over the private network. forge already runs Swarm (Dokploy uses it). On forge, docker swarm join-token worker; on forge-worker, run the join command pointed at forge’s private 10.0.0.x IP on port 2377, not the public one, so the control plane stays off the internet.

  2. Open the swarm ports between the two nodes. This is a new “two-firewall” step, mirroring the Postgres rule:

    • 2377/tcp - cluster management
    • 7946/tcp and 7946/udp - node-to-node gossip
    • 4789/udp - the overlay’s VXLAN data plane

    The VCN Security List already permits all 10.0.0.0/16 traffic, so the cloud firewall is covered, but the instance iptables on each box must also allow these from the peer’s private IP (Oracle’s default iptables is restrictive). Consider --opt encrypted on the overlay to wrap the VXLAN in IPSec.

  3. Label the nodes and constrain the services. Tag the data node and pin the API and PostgreSQL to it:

    Terminal window
    docker node update --label-add role=data forge-worker
    # API and Postgres services deploy with:
    # --constraint node.labels.role==data

    Traefik and the SPAs stay on forge. Nothing about the domains, cookie paths, or build args changes - the API is still reached at app.nexisapi.xyz/api, now routed across the overlay.

This is the real cost. Dokploy’s “Remote Servers” model treats each server as a separate Docker environment, not one joined swarm with placement constraints. So a true single-cluster-with-overlay usually means operating below Dokploy for these services - managing the swarm join, node labels, and constraints yourself (for example, a hand-authored docker stack deploy compose stack) and giving up Dokploy’s UI for the API and database. Before committing, check whether the installed Dokploy version exposes Swarm/cluster placement settings; if it does not, this is a hand-rolled stack.

If Option B is chosen later, it can be done without downtime on the web tier:

  1. Get everything working on forge first (done - the docs site is live this way).
  2. Open the swarm ports on the VCN (iptables on both boxes).
  3. Join forge-worker as a worker over the private IP.
  4. Label the nodes (web / data).
  5. Redeploy the API and PostgreSQL as services constrained to forge-worker; the SPAs and Traefik stay on forge.
  6. Re-run the make-or-break test: sign in, reload, confirm the session survives - proving Traefik still serves /api over the overlay.

For the pilot, Option A is the right call. The payoff of Option B (co-locating the API with PostgreSQL, moving API compute off the web host) does not outweigh the added fragility on two small ARM instances, and it likely pushes these services out of Dokploy’s managed model. Option B becomes worth it later, when the platform is multi-tenant and genuinely needs to separate web from API/DB compute, or to run for high availability. The decision is left open here on purpose.