TL;DR: I cannot reach a service container from a custom Pipe. Is this by design, and what can I do about it?

We're using Tailscale to connect to our internal services. Usually, this doesn't really affect Bitbucket deployments, but recently, we started tracking dependencies and vulnerabilities using Dependency Track, which is hosted in our Tailnet exclusively—so, not reachable from the outside.
Essentially, this means we want to collect information from a built Docker image as an SBOM, and send that in an API request to our Dependency Track instance. I actually created a new (public) Pipe for this: matchory/dependency-track-pipe. It can be used like so:
- pipe: matchory/dependency-track-pipe:0.4.2
variables:
SERVER_URL: "https://dtrack.example.com"
API_KEY: $DTRACK_API_KEYSince we cannot connect directly, we need to run the Tailscale client in the pipeline, and use its SOCKS 5 proxy to forward requests from our pipeline to the server.
The easiest way to do this is to run a Tailscale service container:
definitions:
services:
tailscale:
image: tailscale/tailscale:latest
variables:
KUBERNETES_SERVICE_HOST: ""
TS_KUBE_SECRET: ""
TS_ACCEPT_DNS: "true"
TS_AUTHKEY: $TAILSCALE_AUTHKEY
TS_SOCKS5_SERVER: "localhost:1055"
(If you're also trying to get Tailscale to work, note the Kubernetes variables. These are required to avoid Tailscale detecting the Bitbucket k8s network, which breaks connectivity for your pipeline.)
This allows connecting to all Tailnet services via the SOCKS5 proxy at socks5://localhost:1055. You can verify this by passing the proxy address to curl, for example:
ALL_PROXY=socks5://localhost:1055 \
curl https://some-internal-service.your-tailnet.ts.net/
Plugging all of this together, we want to
- set up a proxy into our Tailscale network,
- collect an SBOM from the container we've just built, and
- send that SBOM to our Dependency Track instance via the proxy.
The following pipeline should be able to achieve this:
definitions:
services:
tailscale:
image: tailscale/tailscale:latest
variables:
KUBERNETES_SERVICE_HOST: ""
TS_KUBE_SECRET: ""
TS_ACCEPT_DNS: "true"
TS_AUTHKEY: $TAILSCALE_AUTHKEY
TS_SOCKS5_SERVER: "localhost:1055"
build-and-push: &build-and-push
# ...
pipelines:
branches:
main:
- step:
name: Build and push
services:
- docker
script:
- *build-and-push
- step:
name: Collect SBOM
services:
- docker
- tailscale
script:
- pipe: matchory/dependency-track-pipe:0.4.2
variables:
ALL_PROXY: socks5://localhost:1055
SERVER_URL: https://dtrack.your-tailnet.ts.net/
# ...
This does not work, however, because the proxy cannot be reached from within the pipe container:
SBOM Upload failed TypeError: fetch failed
at fetch (/pipe/node_modules/undici/index.js:112:13)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async main (file:///pipe/upload.mjs:80:22) {
[cause]: SocksClientError: connect ECONNREFUSED 127.0.0.1:1055
at SocksClient.closeSocket (/pipe/node_modules/socks/build/client/socksclient.js:390:32)
at SocksClient.onErrorHandler (/pipe/node_modules/socks/build/client/socksclient.js:363:14)
at Socket.onError (/pipe/node_modules/socks/build/client/socksclient.js:225:38)
at Object.onceWrapper (node:events:634:26)
at Socket.emit (node:events:519:28)
at emitErrorNT (node:internal/streams/destroy:170:8)
at emitErrorCloseNT (node:internal/streams/destroy:129:3)
at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
options: {
command: 'connect',
proxy: [Object],
timeout: 10000,
destination: [Object],
existing_socket: undefined
}
}
}
I think this may be related to the way pipe networking is set up, but there is nothing in the documentation mentioning limitations in this regard, neither in "Databases and Service containers", nor in "Use Pipes in Bitbucket Pipelines".
I could include Tailscale in the Dependency Track pipe container, but that would make it way too specialised for everyone else to use, and I think that'd be a sad state of affairs for Pipelines.