Freestyle Docs

Freestyle / Docs

VM Domains

Route HTTPS traffic from custom domains to services running inside Freestyle VMs.

VM domains route public HTTPS traffic from a hostname you control to a port inside a Freestyle VM. Create a VM first, then create a TLS ingress rule routing the domain to the VM port that should receive traffic.

Domain Flow

  1. Verify ownership of the domain with a TXT record.
  2. Point DNS at Freestyle with a CNAME to beta-web.freestyle.sh.
  3. Delegate _acme-challenge to beta-dns.freestyle.sh so a certificate can be issued.
  4. Create a TLS rule routing the domain to a VM port.
  5. Run a service in the VM that listens on that port.

To serve every subdomain of a domain rather than named ones, see Wildcard Domains.

Create A VM And Route A Domain To It

Create the VM, start a service inside it, then route the public hostname to the service port with a TLS rule: the open Internet as the source, the VM port as the destination.

import { Freestyle } from "freestyle";

const freestyle = new Freestyle();

const domain = "app.example.com";

const { vm, vmId } = await freestyle.vms.create({
  // Required: a VM reaches nothing it has not been allowed to.
  firewall: { rules: [{ action: "allow", source: {}, destination: { public: true } }] },
});

// Write a small HTTP server into the VM.
await vm.fs.writeTextFile(
  "/root/server.js",
  `
const http = require("http");

http
  .createServer((_req, res) => {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("<h1>Hello from a Freestyle VM</h1>");
  })
  .listen(3000, "0.0.0.0");
`,
);

// Install Node and run the server under systemd, so it stays up and restarts.
await vm.exec("apt-get update && apt-get install -y nodejs");
const node = (await vm.exec("command -v node")).stdout!.trim();

await vm.fs.writeTextFile(
  "/etc/systemd/system/app.service",
  `[Service]
ExecStart=${node} /root/server.js
Restart=always
[Install]
WantedBy=multi-user.target`,
);
await vm.exec("systemctl daemon-reload && systemctl enable --now app");

await freestyle.tls.rules.create({
  action: "allow",
  domain,
  source: { public: true },
  destination: { vmId, port: 3000 },
});

console.log(vmId);

Route A Domain To An Existing VM

const rule = await freestyle.tls.rules.create({
  action: "allow",
  domain: "app.example.com",
  source: { public: true },
  destination: { vmId: "your-vm-id", port: 3000 },
});

console.log(rule.id); // tls-…

Ingress is one shape of a TLS rule. The full grammar — wildcard domains, outbound rules that inject secrets, VM-to-VM services by name — is on the TLS page.

Stop Routing A Domain

Delete the rule to stop routing traffic from the domain to the VM. The domain stays verified, so you can route it again later without re-verifying.

await freestyle.tls.rules.delete(rule.id);

Requirements

  • The domain must be verified before a rule can route it.
  • DNS must point at Freestyle before traffic reaches the VM.
  • HTTPS is provisioned automatically, once _acme-challenge is delegated — issuance is proven over DNS, so without that record the domain resolves but cannot present a certificate.
  • The service inside the VM must listen on the rule’s destination port.
  • For HTTP servers, listen on 0.0.0.0, not only localhost.
esc