Skip to main content
Using Deno in production at your company? Earn free Deno merch.
Give us feedback
Subhosting

Introducing More Flexible Domain Association for Deno Subhosting

Subhosting makes it easy to securely run untrusted JavaScript from multiple customers in a secure, hosted sandbox. There are many use cases for Subhosting, such as offering their users edge functions, hosting ecommerce storefronts close to users, and more - all without worrying about security and maintaining production infrastructure.

When hosting your users’ code, you sometimes want their deployments to be publicly accessible via a neat custom domain like user1.yourcompany.com. Now, managing custom domains across your users’ deployments is simpler with our new, more flexible domain associations. This allows you to programmatically manage domain mappings and attach custom domains to deployments via the Subhosting API.

In this post, we’ll cover:

Before we dive into how to use the API, let’s review what’s now supported with this feature.

Organization-wide wildcard subdomains

You can now assign different subdomains under the same wildcard domain to different deployments. For example, given the *.example.com wildcard domain, you can now assign foo.example.com to one deployment and bar.example.com to another. This flexibility allows for more sophisticated deployment strategies and easier resource management.

Variables for simpler domain management

To make programmatically managing and referencing your users’ deployments simpler, two variables are now exposed when you specify a domain name to associate with a deployment:

  • {deployment.id}: The unique identifier of the deployment.
  • {project.name}: The name of the project to which the deployment belongs.

These can be combined with arbitrary strings as long as they are valid domains and covered by the wildcard domain you registered after substitution. For example, you can specify a domain name like:

  • {deployment.id}.example.com
  • {project.name}.example.com
  • {project.name}-{deployment.id}.example.com
  • foo-{deployment.id}.example.com
  • foo-{deployment.id}-{project.name}.example.com

These variables can also be used in combination with the deno.dev domain, but in this case only the following two formats are allowed:

  • {project.name}-{deployment.id}.deno.dev
  • {project.name}.deno.dev

These enhancements provide better customization and automation capabilities, making it a lot easier to manage and reference deployment programmatically.

Let’s look at how these features are put to practice with an example.

How to attach and detach custom domains

Before you can attach custom domains to deployments, your custom domain first needs to be registered using the POST /organizations/{organizationId}/domains endpoint:

import { assert } from "jsr:@std/assert/assert";

const orgId = "your-organization-id";
const orgToken = "your-organization-token";

const res = await fetch(
  `https://api.deno.com/v1/organizations/${orgId}/domains`,
  {
    method: "POST",
    body: JSON.stringify({
      domain: "*.example.com",
    }),
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${orgToken}`,
    },
  },
);

assert(res.ok);

The response body of this call contains dnsRecords field, which is a list of DNS records that you can use to setup a name server.

The next step is to issue TLS certificates for the domain. While you can do so by uploading certificates, you can also provision TLS certificates via POST /domains/{domainId}/certificates/provision:

import { assert } from "jsr:@std/assert/assert";

const orgToken = "your-organization-token";
// Domain ID you got from the previous step
const domainId = "your-domain-id";

const res = await fetch(
  `https://api.deno.com/v1/domains/${domainId}/certificates/provision`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${orgToken}`,
    },
  },
);

assert(res.ok);

Now you’re all set — let’s create a new deployment with POST /projects/{projectId}/deployments:

import { assert } from "jsr:@std/assert/assert";

const projectId = "your-project-id";
const orgToken = "your-organization-token";

const res = await fetch(
  `https://api.deno.com/v1/projects/${projectId}/deployments`,
  {
    method: "POST",
    body: JSON.stringify({
      entryPointUrl: "main.ts",
      assets: {
        "main.ts": {
          kind: "file",
          content: 'Deno.serve(() => new Response("hello"));',
        },
      },
      envVars: {},
      domains: [
        "foo.example.com",
        "{deployment.id}.example.com",
        "{project.name}-{deployment.id}.deno.dev",
      ],
    }),
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${orgToken}`,
    },
  },
);

assert(res.ok);

Note the new domains field in the JSON payload, which is an array of the custom domains that will be attached to the deployment once it’s successfully created. Also, here is where you can use named variables in the domain, which will become actual values.

Let’s say that the newly created deployment ID is chonky-dog-57 under the project my-project. In our example, the following domains will all route to this newly created deployment:

  • https://foo.example.com
  • https://chonky-dog-57.example.com
  • https://my-project-chonky-dog-57.deno.dev

What if we want to attach a custom domain to an existing deployment? We can use PUT /deployments/{deploymentId}/domains/{domain}:

import { assert } from "jsr:@std/assert/assert";

const deploymentId = "chonky-dog-57";
const orgToken = "your-organization-token";

const extraDomain = "prefix-{project.name}.example.com";

const res = await fetch(`/deployments/${deploymentId}/domains/${extraDomain}`, {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${orgToken}`,
  },
});

assert(res.ok);

Now, anyone visiting https://prefix-my-project.example.com will be directed to the existing chonky-dog-57 deployment.

Note that anytime a domain is attached to a deployment, it’s automatically detached from any previous deployment.

We can also manually detach a domain from a deployment with DELETE /deployments/{deploymentId}/domains/{domain}:

import { assert } from "jsr:@std/assert/assert";

const deploymentId = "chonky-dog-57";
const orgToken = "your-organization-token";

const res = await fetch(
  `/deployments/${deploymentId}/domains/foo.example.com`,
  {
    method: "DELETE",
    headers: {
      "Authorization": `Bearer ${orgToken}`,
    },
  },
);

assert(res.ok);

Now, the domain https://foo.example.com will no longer direct to the chonky-dog-57 deployment.

What’s next

Subhosting continues to be a great choice for organizations interested in running their users’ code without worrying about security or maintaining production infrastructure.

We’ll continue to invest resources in making Subhosting the easiest way to run third party untrusted code securely, so you can focus on delivering value to your end users.

🚨️ Want to run users’ untrusted code securely without building it from scratch?

Check out Deno Subhosting, where you can run JavaScript from multiple users in a secure, hosted sandbox in minutes.