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.
Minimal Example
Section titled “Minimal Example”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"], }, },});With IPv4 Address
Section titled “With IPv4 Address”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 }, },});With IPv6 Address
Section titled “With IPv6 Address”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 }, },});With Dual Stack
Section titled “With Dual Stack”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 }, },});With Custom Ports
Section titled “With Custom Ports”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"], }, },});Bind to a Worker
Section titled “Bind to a Worker”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"); },};With Existing Tunnel ID
Section titled “With Existing Tunnel ID”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"], }, },});Adopting Existing Services
Section titled “Adopting Existing Services”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 }, },});Host Configuration Options
Section titled “Host Configuration Options”Hostname Host
Section titled “Hostname Host”Use DNS resolution to reach the service:
| Property | Type | Description |
|---|---|---|
hostname | string | The hostname to resolve |
resolverNetwork.tunnel | Tunnel | The tunnel resource to use |
resolverNetwork.tunnelId | string | Alternative: existing tunnel ID |
resolverNetwork.resolverIps | string[] | Optional DNS resolver IPs |
IP Address Host
Section titled “IP Address Host”Use a direct IP address:
| Property | Type | Description |
|---|---|---|
ipv4 | string | IPv4 address of the service |
ipv6 | string | IPv6 address of the service |
network.tunnel | Tunnel | The tunnel resource to use |
network.tunnelId | string | Alternative: existing tunnel ID |
Port Configuration
Section titled “Port Configuration”| Property | Type | Default | Description |
|---|---|---|---|
httpPort | number | 80 | Port for HTTP traffic |
httpsPort | number | 443 | Port for HTTPS traffic |
tcpPort | number | - | Port for TCP traffic (future support) |
appProtocol | string | - | Application protocol identifier |
Access Control
Section titled “Access Control”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.