tako docs guide reference api
GitHub

tako.ServiceConfig.Domain

A custom domain with a Google-managed certificate, at no standing infrastructure cost. It is the one option that belongs to no single constructor: it addresses a service, and Site, API, and Page can all declare it.

Declare

ServiceConfig{Domain: fqdn} goes on tako.Service, the package's app-service declaration. Both of these are complete:

// A site's service.
var (
	web  = tako.Service(tako.ServiceConfig{Domain: "blog.example.com"})
	site = tako.Site("./site/public",
		tako.SiteConfig{Build: tako.Build{Command: "cd site && hugo --minify", Out: "./site/public"}})
)

// An API-only service — no tako.Site anywhere in the package.
var (
	api     = tako.Service(tako.ServiceConfig{Domain: "api.example.com"})
	Create  = tako.API("POST /api/links", create)
	Resolve = tako.API("GET /api/links/{slug}", resolve)
	List    = tako.API("GET /api/links", list)
)

One declaration addresses every route in the service. No endpoint carries the claim, because none of them is a more honest place to put it than any other — that is exactly why tako.Service exists.

Declaring it also NAMES the service <package>-<var>: api in package shortlink gives shortlink-api. See service.md.

Reading it back

The declaration is analysis-time configuration, and it stays that way: the analyzer reads the domain from the source literal, a variable is still refused (tako/config-literal), and the deploy plan is still computed without running your app. But the app can read back what it declared:

var web = tako.Service(tako.ServiceConfig{Domain: "example.com"})

func canonical(path string) string { return "https://" + web.Domain() + path }

Absolute URLs are the reason — canonical tags, RSS <link> elements, sitemap entries, share links. Without this the domain gets written twice, once where tako reads it and once where your code builds a URL, and the two drift silently.

Domain() returns "" when no domain was declared. It does not fall back to the run.app URL, and it does not become localhost under tako dev: the value is the declared one in every environment, because the things absolute URLs are for name the public address of the site regardless of where the code is running.

Which service, exactly

This is the part worth being explicit about, because tako never asks you to group endpoints into a service — it derives the grouping:

So tako.Service describes the package's APP service — the one running its API, Page and Cron handlers — and never the satellites. tako gen graph shows you the answer before you deploy:

shortlink: 4 services
  service shortlink-api (package shortlink, profile default, domain api.ker.gs)
    POST /api/links → Create
    GET /api/links/{slug} → Resolve
    GET /api/links → List
  service shortlink-countclick (package shortlink, profile default)
    worker CountClick ← queue clicks
  …

One package, four services, and the domain sits on exactly one of them. The workers get no domain and could not take one: they receive work over authenticated push, never public traffic.

One service, one domain, and one tako.Service per package (tako/one-service), so two names in one service is not a state you can reach.

Envs do not prefix the domain the way they prefix every other resource name (u1-shortlink, u1-clicks). A domain is globally unique by nature, so two environments declaring the same one is a contradiction only you can resolve; the second deploy fails with the 409 below rather than tako inventing u1-api.example.com.

Cloud

tako deploy creates a Cloud Run domain mapping — no load balancer, no bucket, no standing charge. The plan prints a ⚠ about MATURITY rather than money, because Google ships domain mappings as preview and does not recommend them for production. See docs/domain-options.md for what tako deliberately did not build, what those cost, and what would make tako switch. Switching would be a tako change, never a change to your code.

Two steps are yours, and deploy names both:

  1. Prove you own the domain, once per project. Preflight refuses until then: gcloud domains verify example.com (it opens Search Console). Verifying a parent covers its subdomains.
  2. Publish the DNS records deploy prints:
  domain  api.ker.gs               awaiting DNS
    create these records in the DNS zone for ker.gs:
      api                          CNAME  ghs.googlehosted.com.
    then re-run tako deploy — the certificate provisions once the
    records resolve (usually ~15 minutes, up to 24 hours).

Names come back RELATIVE to the zone, which is what a registrar's host field wants; an apex domain shows @ with A/AAAA records. Deploys are stateless, so re-running just reports where the certificate got to — including when nothing else changed:

  domain  api.ker.gs               certificate ACTIVE → https://api.ker.gs

A mapping adds an address; it does not close one. The run.app URL keeps serving, and the security summary says so:

  public ingress on api.ker.gs AND its run.app URL (a domain mapping adds an address, it does not close one)

Re-pointing a domain at a different service is a REPLACE, not an update: the mapped route is immutable, so deploy prompts before deleting and recreating, and says the domain goes dark until the new certificate issues.

tako destroy removes the mapping first, before the service it points at. It never touches your DNS records or your domain verification — those live at your registrar and in your Google account.

Local

Nothing. tako dev serves on localhost; a domain is a cloud address and the local backend has no equivalent to fake. The declaration is inert locally, which is why it is safe to leave in your code.

Test it

The declaration is analyzer-level, so assert it on the graph rather than over HTTP:

graph, _ := analyzer.Analyze(analyzer.Config{Dir: "."})
// graph.Services[i].Domain == "api.example.com"

tako gen graph is the same answer, human-readable.

Errors you'll meet

At analysis time:

tako.ServiceConfig.Domain needs a bare domain name — no scheme, port, path, or trailing dot; got "https://blog.example.com/" (tako/domain)
tako.ServiceConfig.Domain needs a fully-qualified domain — "blog" has no dot (tako/domain)
tako.ServiceConfig.Domain does not support wildcards — got "*.example.com"; declare each subdomain on its own service (tako/domain)
this package declares the domain "blog.example.com", but nothing here serves public traffic — queue workers, topic subscribers, and jobs are reached over authenticated push or not at all, so the name would answer nothing; declare an API, Page or Site route here, or move the domain to the package that has one (tako/domain-unreachable)
a package declares one service — oneservice.api is already declared; remove oneservice.admin or merge the two (tako/one-service)

At deploy time, before anything is built:

Cloud Run domain mappings are not available in europe-west2 — deploy to one of asia-east1, asia-northeast1, asia-southeast1, europe-north1, europe-west1, europe-west4, us-central1, us-east1, us-east4, us-west1 (tako deploy --region <region>), or drop the Domain field and use the run.app URL
blog.example.com is not verified for tako-test-02 — prove you own it once: gcloud domains verify example.com (this opens Search Console), then re-run tako deploy

And if the name is already spoken for:

blog.example.com is already mapped in tako-test-02 — a domain answers for one service, and tako will not take it from whatever holds it; remove that mapping (gcloud beta run domain-mappings delete --domain blog.example.com --region us-central1) or choose another name

Not yet

No edge caching. Your Cloud Run service serves every byte; the runtime's caching headers work at the browser, not at an edge. There is no CDN option — see docs/domain-options.md.

About ten regions: asia-east1, asia-northeast1, asia-southeast1, europe-north1, europe-west1, europe-west4, us-central1, us-east1, us-east4, us-west1.

No wildcards, no bring-your-own certificate, root path only (you cannot map example.com/blog to one service and example.com/shop to another — that needs the load balancer), 64-character limit, and TLS 1.0 and 1.1 cannot be disabled.