Custom domains, TCP & private networks
HTTP Services already get a generated
*.up.railway.app hostname. That is enough for most apps. Add a
CustomDomain for a hostname you
control. Add a TcpProxy so a laptop
or deploy-time migration can reach Postgres, MySQL, Mongo, or Redis.
Named PrivateNetworks add
custom DNS on the default *.railway.internal mesh.
Publish the Service
Section titled “Publish the Service”A Service with port listens inside the container. Alchemy creates
a *.up.railway.app domain and points it at that port.
export default class Api extends Railway.Service<Api>()( "Api", { project: Site, main: import.meta.url, port: 3000, }, Effect.gen(function* () { return { fetch: Effect.succeed(HttpServerResponse.text("hello")), }; }),) {}Yield the Service in the Stack. api.url is
https://{name}.up.railway.app.
export default Alchemy.Stack( "MyApp", { providers: Railway.providers(), state: Alchemy.localState() }, Effect.gen(function* () { const api = yield* Api; return { url: api.url }; }),);There is no IpAssignment resource. Railway’s edge terminates TLS on 443.
Custom domains
Section titled “Custom domains”A CustomDomain is a user hostname on a Service. Railway issues a Let’s Encrypt certificate once DNS is verified.
const site = yield* Railway.Project("Site");const api = yield* Railway.Service("Api", { project: site, image: "hashicorp/http-echo", port: 5678,});const www = yield* Railway.CustomDomain("Www", { service: api, environment: site, domain: "www.example.com",});Yield Www in the Stack. Point DNS at
www.verificationDnsHost / www.verificationToken. www.url is
https://www.example.com. verified flips once Railway sees the
record.
targetPort is optional and updates in place. Changing domain,
service, or environment replaces the CustomDomain.
const www = yield* Railway.CustomDomain("Www", { service: api, environment: site, domain: "www.example.com", targetPort: 8080,});TCP proxies
Section titled “TCP proxies”A TcpProxy exposes a service over public TCP
({domain}:{proxyPort} on *.proxy.rlwy.net). Identity is the
Railway proxy id. There is no in-place update — changing service,
postgres, redis, environment, or applicationPort replaces
the proxy.
Railway.Postgres,
MySQL, and Mongo
already create one when public is true (the default). Use
TcpProxy for Redis, or for a database created with public: false.
const site = yield* Railway.Project("Site");const db = yield* Railway.Postgres("Db", { project: site, public: false });const proxy = yield* Railway.TcpProxy("DbProxy", { postgres: db, environment: site, applicationPort: 5432,});Redis on 6379:
const cache = yield* Railway.Redis("Cache", { project: site });const proxy = yield* Railway.TcpProxy("CacheProxy", { redis: cache, environment: site, applicationPort: 6379,});Pass a Railway.Service (or { serviceId }) for any other
container.
In-service bindings (ConnectPostgres, ConnectMySQL,
ConnectMongo, ReadWriteRedis) always use the private
{name}.railway.internal URL. The TCP proxy is for laptops and CI.
Private networks
Section titled “Private networks”Every environment already has the default *.railway.internal mesh
— {serviceName}.railway.internal is derived from the Service name,
no extra resource. PrivateNetwork
create-or-gets an additional named network. Attach a Service with
PrivateNetworkEndpoint
for a custom DNS name.
const net = yield* Railway.PrivateNetwork("Mesh", { environment: site,});const endpoint = yield* Railway.PrivateNetworkEndpoint("ApiDns", { network: net, service: api, name: "api",});Railway has no per-network delete. Destroy is a no-op; the network is removed when its Project/Environment is deleted.
Where next
Section titled “Where next”The tutorial deploys a Service that
answers on *.up.railway.app. Custom domains and TCP proxies hang
off the Service / Postgres / Redis. See the
CustomDomain,
TcpProxy,
PrivateNetwork (includes
PrivateNetworkEndpoint) references.