Network isolation
Every environment gets its own private network, so a service name means one container and nothing crosses an environment boundary.
What it is
Each environment gets its own private Docker network. The apps and managed databases placed there reach each other by name. Nothing outside reaches in.
Apps that are not in a project sit on their team's network instead, so a solo setup behaves exactly as it always did: everything you own can talk to everything else you own.
Without projects, nothing is separated
Everything outside a project - the top level, and anything in a folder - shares one network per team. That is deliberate (it is what keeps the simple setup simple), but it means the boundary only exists once you use environments. If you want two things separated, put them in different environments; a folder will not do it.
You never create or pick a network. Deplo derives it from where the app lives, and the proxy joins it on every deploy.
What sees what
| From | To | Reachable |
|---|---|---|
| A service in a stack | Another service in the same stack | Yes |
| An app | Another app in the same environment, same server | Yes |
| An app | A database in the same environment, same server | Yes |
| An app | Anything in the same environment on another server | No |
| An app in Production | An app in Staging, same project | No |
| An app | An app in another project | No |
| An app | Anything of another team | No |
| A pull request preview | Anything at all | No |
| An app | A published port on the same server | Yes - see below |
The "no" rows for another project and another team are the point. Before this, every container on a server shared one network: any app could open a connection to any other app's database, by name or by address, whoever owned it.
A preview cannot reach your database
A pull request preview is sealed in a network of its own, because the code in a pull request is a stranger's. That also means it does not reach the managed database of the environment it was opened from - the preview will fail to connect unless it brings its own.
Give the preview its own database as a service inside the compose file, or point it at a database you have published a port for. There is no way to let a preview into an environment's network: that is the thing the seal is for.
A published port is not behind the boundary
A port published on the host (an app's ports:, or a database you exposed) is
bound on the machine itself, so any container on that server reaches it,
whatever team or environment it belongs to - through the docker gateway or the
server's own address. Closing the port in ufw does not change that: the traffic
never crosses the public interface.
Publish a port only for what you mean to make reachable. To let one app talk to another, put them in the same environment and use the name - that path IS behind the boundary.
The proxy answers from every network
Traefik sits on every environment's network in order to route it, so a container can reach it directly and ask for any hostname this server serves - including apps of other teams, and including a request that never went through Cloudflare, an IP allowlist, or a WAF in front of your domain.
Anything you give a domain is public in that sense. Keep what must not be reached without a domain, and it stays inside its environment.
A network does not span servers
A Docker network lives on one machine. Two apps in the same environment but on different servers do not reach each other by name - that was true before this change too, and no placement fixes it. Put both on the same server, or publish a port and use the server's address. A deploy that points at a name on another server says so in its log.
Folders have no network of their own. Moving an app between folders, or in and out of one at the team level, never changes what it can reach.
Moving an app from an environment into a folder is a different move. An app lives in exactly one place, so filing it in a folder takes it out of the environment and puts it on the team's network - it leaves that environment's databases behind. Deplo brings the stack up again on the new network before the move returns, and the deploy log names anything it can no longer resolve.
A folder is permissions, not a network
Two apps in different folders are on the same team network and reach each other by name. Folders decide who may see and change an app, not what an app may talk to. If two things must not reach each other, put them in different environments.
Moving an app into a folder does change its network, though - not because of the folder, but because an app lives in one place, so filing it into a folder takes it out of its environment and onto the team's. Deplo brings the stack up again on the new network, and refuses the move if a name it answers to is already taken there.
A domain is a separate question. Anything you give a hostname is served by the proxy, and the proxy answers from every network - so an app with a domain is reachable at that domain from inside another environment exactly as it is from the internet. The boundary is about names and addresses inside the machine, not about what you publish.
Which services are on the network
Every service in the stack, whatever else its compose file names. Deplo adds
the environment's network to the ones you declared, it never replaces them - so a
frontend/backend split keeps working exactly as written, and a worker with no
domain reaches db-<name> like everything else.
Three kinds of service stay off it:
- One with
network_mode:. It joins no network of its own by definition, so there is nothing to add it to - and a domain pointed at it will not answer. - One whose name, or
hostname:, is one the platform answers to (postgres,traefik,deplo, and the three others). It stays on the stack's own private network, and Deplo keeps every other service on that network too, so the app still reaches it by name. - One whose networks you all marked
internal: true. That is a deliberate seal and Deplo leaves it alone. A domain still wins: route to the service and it joins, because otherwise the proxy cannot reach it.
services:
web:
image: my/app
networks: [backend] # on backend AND the environment's network
worker:
image: my/app # names nothing: on the environment's network
vault:
image: vault
networks: [sealed] # sealed off on purpose: stays private
networks:
backend: {}
sealed:
internal: trueWhere a database lives
A managed database takes a placement, exactly like an app. You pick it when you create it, under Storage → New database.
- In an environment - the apps in that environment reach it at
db-<name>. This is what you want almost always. - No environment - it sits on the team's network, reachable from apps that are not in a project.
Where a database lives is on its Networking card, next to the internal address it decides, and you change it from the same place - the container is brought up again on the new network before the action returns. The database list's card menu has the same Move to environment.
A database you stopped stays stopped: it follows the move the next time you start it.
A published port is outside the boundary
Exposing a database on a host port takes it out of all of this. Docker binds the port on the machine, so it answers every container on that server, whatever network they are on - and the server's own firewall does not stop them, because Docker publishes below it. The password is the only thing in the way. Leave the port off unless something outside the server really needs it.
Two stacks, one name
Inside one environment the names have to be unique: two apps that both have a
service called web both answer to web on that network, and Docker splits the
lookups between them. Half your requests reach the wrong container.
Saving a compose file that takes a name already answered there is refused, and the message names one that is free:
`web` is already answered by Shop on that network, and Docker would split the
connections between the two. Rename it on this app (the service, or its
`hostname:`) - `web-2` is free - or pick another environment.An overlap that already exists - two stacks that landed there before the check, or arrived by different routes - is reported by the deploy instead:
`web` is also answered by a stack in Shop / Production, on the same network as
this one. Docker splits the lookups between them, so half will reach the wrong
container.Rename the service, or give it a hostname: of its own. Every service of your
stack takes a name on that network, not only the ones you route: a worker with no
domain is still part of the app and still has to reach your database.
Declaring your own networks does not take a service off it - frontend and
backend is organisation, not a request to be cut off, and your networks are kept
alongside the environment's. Three things do stay off:
| Stays off | Why |
|---|---|
A network you marked internal: true | That IS the request to be cut off, and adding the environment's network would hand back the egress you removed |
network_mode: on a service | It replaces the network stack entirely, so there is nothing to join - and a domain pointing at such a service is reported in the deploy log |
| A service named after Deplo's own infrastructure | Left off rather than refused, so an ordinary stack still deploys. It keeps a private network with the rest of your stack, so the two still talk |
Deplo also refuses a move that would create the overlap - putting an app into an environment where a database already answers to one of its names, or the reverse.
A stack Deplo wrote is renamed rather than refused: a template you deploy, or
an app a migration brings over from another panel. Deploying the same template
twice into one environment gives the second one myapp-db instead of db, and
everything that named it moves with it - the route, the variables, the config
files. The app's Activity says what was renamed; a migration puts it in the report
too. A compose file you wrote yourself is yours, so it is refused with the free
name instead of edited behind your back.
cannot resolve host
The one thing that breaks: an app pointing at another app by name, when the two are in different environments.
could not translate host name "gamewatcher-db" to address:
Name or service not knownDeplo says so before the container tries. The deploy log carries a line naming what you pointed at and where it lives:
DATABASE_URL points at `gamewatcher-db`, which lives in GW / Production and
is not reachable from here.The other shape says the placement is right and the machine is not:
DB_HOST points at `orders-db`, which is in Shop / Production with this app but
runs on ANOTHER SERVER.Three ways out, best first:
Put them in the same environment, on the same server
Move the app (or the database) so both sit in one environment and on one server. That is what the boundary is for - and the server part is not the boundary's doing, it is what a Docker network is.
Use a managed database
Create it in the environment that needs it. Deplo writes the connection string and keeps it reachable.
Publish a port
Only if the two genuinely must stay apart: expose the service on a host port and point at the server address instead of the container name. Treat that port as public - it answers every container on the server, not only the one you opened it for.
Upgrading an existing instance
The first time a Deplo with this feature starts, it moves every existing app and database onto its environment's network, one server at a time.
- A stack that cannot be reached stays where it is and keeps running. It is a delay, not an outage.
- The Overview shows how many the sweep could not reach, and Activity says which ones and why. A stack it deliberately skipped - one you had stopped, one mid-deploy - is not in that count.
- Deploying one moves it. Try again on the notice re-runs the whole sweep.
Databases that had no placement are given the environment that actually uses them, when exactly one place does. One used from two environments - or from an environment and from apps outside every project - stays at the team level for you to decide, because moving it would take it away from half its callers.
Stopped apps and stopped databases are left alone and follow when you start them. So does anything that was mid-deploy, on its next deploy.
What stays exactly the same
Domains, HTTPS, certificates, redirects and basic auth. Published ports, for apps and for databases. Logs, console, terminal, backups, cron, metrics and migrations - all of those reach a container through the server agent, never through the Docker network.
Did this page help you?