API placement: single-server vs single-swarm
The question
Section titled “The question”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:
/api across a swarm overlay to the API on forge-worker, so the public origin never changes.The two options
Section titled “The two options”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.
Option B - one swarm, API on forge-worker
Section titled “Option B - one swarm, API on forge-worker”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.
Trade-offs
Section titled “Trade-offs”| 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 |
What Option B takes
Section titled “What Option B takes”-
Join the swarm over the private network.
forgealready runs Swarm (Dokploy uses it). Onforge,docker swarm join-token worker; onforge-worker, run the join command pointed at forge’s private10.0.0.xIP on port2377, not the public one, so the control plane stays off the internet. -
Open the swarm ports between the two nodes. This is a new “two-firewall” step, mirroring the Postgres rule:
2377/tcp- cluster management7946/tcpand7946/udp- node-to-node gossip4789/udp- the overlay’s VXLAN data plane
The VCN Security List already permits all
10.0.0.0/16traffic, so the cloud firewall is covered, but the instanceiptableson each box must also allow these from the peer’s private IP (Oracle’s defaultiptablesis restrictive). Consider--opt encryptedon the overlay to wrap the VXLAN in IPSec. -
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==dataTraefik and the SPAs stay on
forge. Nothing about the domains, cookie paths, or build args changes - the API is still reached atapp.nexisapi.xyz/api, now routed across the overlay.
The Dokploy caveat
Section titled “The Dokploy caveat”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.
Migration path (incremental)
Section titled “Migration path (incremental)”If Option B is chosen later, it can be done without downtime on the web tier:
- Get everything working on
forgefirst (done - the docs site is live this way). - Open the swarm ports on the VCN (
iptableson both boxes). - Join
forge-workeras a worker over the private IP. - Label the nodes (
web/data). - Redeploy the API and PostgreSQL as services constrained to
forge-worker; the SPAs and Traefik stay onforge. - Re-run the make-or-break test: sign in, reload, confirm the session survives - proving Traefik still serves
/apiover the overlay.
Recommendation
Section titled “Recommendation”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.