Docker Compose
Add one Portr service to your docker compose file and tunnel any service by labelling it. Tunnels start and stop with the containers.
If your development stack runs on Docker Compose, you don't need a portr
command per service. Add one portr service to the compose file, label the
services you want public, and docker compose up brings the tunnels up with
them.
web is served at my-app.<your portr domain>, and db gets a public TCP
port, printed in the portr service logs:
How it works
The portr service runs portr docker, which talks to the Docker daemon
through the mounted socket:
- It finds its own container and reads its compose project name.
- It subscribes to container
startanddieevents, filtered to that project and to containers labelledportr.enable=true. - When a labelled service starts, it opens a tunnel to the service's IP on the compose network. When the service stops, the tunnel closes. Restarting a service gives it the same subdomain again.
Only containers in the same compose project are watched, so other stacks on
the machine are never exposed, even if they carry portr.* labels. When you
combine several files with -f, they form one project and are watched
together.
Configuration
The portr service reads the same client config file as the CLI. Mount the
one created by portr auth at /root/.portr/config.yaml. Connection settings
such as server_url, secret_key, and transport are used as-is. The
tunnels and groups sections are ignored, because in docker mode tunnels
come only from labels.
Inside the container, localhost is the container itself. If you run the
Portr server on your own machine, point server_url and ssh_url at
host.docker.internal and add
extra_hosts: ["host.docker.internal:host-gateway"] to the portr service.
Labels
| Label | Default | Description |
|---|---|---|
portr.enable | — | Set to true to tunnel the service. Required. |
portr.type | http | http or tcp. |
portr.port | the only exposed port | Port the service listens on inside its container. Required when the image exposes no port or several ports. |
portr.subdomain | <service>-<project> | HTTP only. TCP tunnels get a port assigned by the server instead. |
portr.name | service name | Label shown in logs and the inspector. |
portr.basic_auth | — | HTTP only. user:password required to reach the tunnel. |
portr.host_header | — | HTTP only. Host header sent to the service. Use rewrite for the local address, or any hostname. |
Unknown portr.* labels are rejected, so a typo such as portr.prot is
reported instead of silently ignored. A service with invalid labels is skipped
with an error in the logs until it is restarted, and the other tunnels keep
running. If a tunnel fails to start, for example while the Portr server is
unreachable, it is retried with backoff, up to every five minutes.
The default subdomain is derived from names you control, so two people
running the same project against one Portr server will collide. Set
portr.subdomain explicitly, for example from an environment variable:
portr.subdomain: "${USER}-app".
Networking
The portr service must share a network with every service it tunnels. With
the default compose network this is automatic. If you use custom networks,
attach portr to each of them. Services using network_mode: host or
network_mode: service:... cannot be tunnelled this way.
Command options
| Flag | Environment variable | Description |
|---|---|---|
--docker-host | DOCKER_HOST | Docker daemon address. Defaults to unix:///var/run/docker.sock. tcp:// is also supported. |
--project | PORTR_DOCKER_PROJECT | Watch this compose project instead of the one the portr container belongs to. |
Mounting the Docker socket gives the container root-equivalent access to the host. Only use this for local development stacks.