Skip to content
GitHubXDiscordRSS

VpcService

Connect Cloudflare Workers to private network services securely through Cloudflare Tunnel.

Cloudflare VPC Services enable Workers to securely access private network resources through Cloudflare Tunnel.

Create a VPC service that routes to a local hostname through a tunnel:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("my-tunnel", {
ingress: [{ service: "http://localhost:3000" }],
});
const vpcService = await VpcService("my-service", {
host: {
hostname: "localhost",
resolverNetwork: {
tunnel,
resolverIps: ["127.0.0.1"],
},
},
});

Route to a service using an IPv4 address:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("internal-tunnel", {
ingress: [{ service: "http://192.168.1.100:8080" }],
});
const vpcService = await VpcService("internal-api", {
host: {
ipv4: "192.168.1.100",
network: { tunnel },
},
});

Route to a service using an IPv6 address:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("ipv6-tunnel", {
ingress: [{ service: "http://[::1]:8080" }],
});
const vpcService = await VpcService("ipv6-service", {
host: {
ipv6: "::1",
network: { tunnel },
},
});

Route to a service that supports both IPv4 and IPv6:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("dual-stack-tunnel", {
ingress: [{ service: "http://localhost:8080" }],
});
const vpcService = await VpcService("dual-stack-service", {
host: {
ipv4: "192.168.1.100",
ipv6: "::1",
network: { tunnel },
},
});

Configure custom HTTP and HTTPS ports:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("custom-port-tunnel", {
ingress: [{ service: "http://localhost:5173" }],
});
const vpcService = await VpcService("dev-server", {
httpPort: 5173,
httpsPort: 5174,
host: {
hostname: "localhost",
resolverNetwork: {
tunnel,
resolverIps: ["127.0.0.1"],
},
},
});

Use a VPC service binding in a Cloudflare Worker to access private services:

import { Tunnel, VpcService, Worker } from "alchemy/cloudflare";
const tunnel = await Tunnel("api-tunnel", {
ingress: [{ service: "http://internal-api:8080" }],
});
const vpcService = await VpcService("private-api", {
httpPort: 8080,
host: {
hostname: "internal-api",
resolverNetwork: {
tunnel,
resolverIps: ["10.0.0.1"],
},
},
});
const worker = await Worker("api-gateway", {
entrypoint: "./src/worker.ts",
bindings: {
PRIVATE_API: vpcService,
},
});

Then in your Worker code, use the binding to fetch from the private service:

export default {
async fetch(request: Request, env: { PRIVATE_API: Fetcher }) {
// The VPC service routes this request through the tunnel
// to your private network
return await env.PRIVATE_API.fetch("http://internal-api/data");
},
};

Reference an existing tunnel by its ID instead of using a Tunnel resource:

import { VpcService } from "alchemy/cloudflare";
const vpcService = await VpcService("existing-tunnel-service", {
host: {
hostname: "internal.example.com",
resolverNetwork: {
tunnelId: "e6a0817c-79c5-40ca-9776-a1c019defe70",
resolverIps: ["10.0.0.53"],
},
},
});

Take over management of an existing VPC service:

import { Tunnel, VpcService } from "alchemy/cloudflare";
const tunnel = await Tunnel("adopted-tunnel", {
ingress: [{ service: "http://localhost:3000" }],
});
const vpcService = await VpcService("adopted-service", {
name: "existing-service-name",
adopt: true,
host: {
hostname: "localhost",
resolverNetwork: { tunnel },
},
});

Use DNS resolution to reach the service:

PropertyTypeDescription
hostnamestringThe hostname to resolve
resolverNetwork.tunnelTunnelThe tunnel resource to use
resolverNetwork.tunnelIdstringAlternative: existing tunnel ID
resolverNetwork.resolverIpsstring[]Optional DNS resolver IPs

Use a direct IP address:

PropertyTypeDescription
ipv4stringIPv4 address of the service
ipv6stringIPv6 address of the service
network.tunnelTunnelThe tunnel resource to use
network.tunnelIdstringAlternative: existing tunnel ID
PropertyTypeDefaultDescription
httpPortnumber80Port for HTTP traffic
httpsPortnumber443Port for HTTPS traffic
tcpPortnumber-Port for TCP traffic (future support)
appProtocolstring-Application protocol identifier

To use VPC Services, users need the appropriate Cloudflare roles:

  • Bind to services: Requires “Connectivity Directory Bind” role
  • Create/manage services: Requires “Connectivity Directory Admin” role

If you use alchemy login, these scopes are included by default.