Skip to content

Getting started

Coming from DNSControl

Migrate from DNSControl to DNSMigrator by mapping creds.json connections, importing dnsconfig.js, replacing preview and push, and checking known differences.

7 min read

On this page

How to migrate from DNSControl#

Compare the ongoing-management options in DNSControl alternatives: Terraform, octoDNS, and a web UI before deciding which zones to import.

DNSControlDNSMigratorWhere to use it
creds.json entryConnectionConnections → Connect a provider
NewDnsProviderConnection mapping plus a managed-zone providerZones → Add zone → dnsconfig.js
NewRegistrarRetained registrar metadata; connection selected separatelyConnections, then Settings → Registrar account or migration cutover
dnsconfig.jsSaved managed-zone draftZone → Config as code
previewPer-provider planZone → Preview changes → Changes
pushTyped-confirmation pushZone → Changes → Push to N providers
get-zonesImport a provider zone or select a migration sourceZones → Add zone → Import from a provider; New migration
check-credsAccess verificationCreating a connection; Connections → Check access
checkLive zone checksZone → Records → Zone checks; CLI dnsmigrator lint
fmtNormalized BIND outputZone file validator; CLI dnsmigrator export
print-irCanonical JSON exportCLI dnsmigrator export --format json
initConnect and add-zone flowsConnect a provider, then Add zone

A one-time migration uses a separate New migration workflow with source translation, destination apply, authoritative verification, rollback, and optional registrar cutover. A managed zone is closer to DNSControl’s ongoing desired-state loop. See How it works.

Recreate creds.json as connections#

Do not paste a creds.json file into the app. Open Connections, choose Connect a provider, and create one connection for each DNS or registrar account you need. The provider form validates its own credential fields and immediately checks access.

Use a name that preserves the intent of the old credkey, such as cloudflare_production. During dnsconfig.js import, DNSMigrator shows each declared DnsProvider key and lets you match it to an existing connection. The first connection with a matching provider is suggested automatically, so verify the selection when the workspace has several accounts at that provider.

creds.json
{
  "cloudflare_production": {
    "TYPE": "CLOUDFLAREAPI",
    "apitoken": "replace-me"
  }
}

The equivalent is not a new JSON file. It is a Cloudflare DNS connection named cloudflare_production, created with the token field shown by the provider guide. See Connecting providers and Credential security.

Import dnsconfig.js#

Open Zones, choose Add zone, then choose dnsconfig.js under How to add the zone. Use Open file or paste the script, then choose Read the config.

dnsconfig.js
var REG_NONE = NewRegistrar("none");
var DSP_CLOUDFLARE = NewDnsProvider("cloudflare_production", "CLOUDFLAREAPI");

D("example.com", REG_NONE,
  DnsProvider(DSP_CLOUDFLARE),
  DefaultTTL(300),
  A("@", "192.0.2.10"),
  CNAME("www", "@"),
  MX("@", 10, "mail.example.net."),
  IGNORE("_acme-challenge", "TXT"),
  NO_PURGE,
);

The script runs in a sandboxed Web Worker in your browser. The importer has no network access, stops after its execution limit, and rejects files over the UI’s import limit. It reports zones, record counts, warnings and errors before anything is saved.

For each declared provider, use Match each DnsProvider to a connection. Selecting Skip for now keeps the zone import but does not attach that provider connection. Choose Import N zones when the mapping is correct. Existing zones in the workspace are skipped rather than overwritten by a bulk config import.

After import, open each zone. Config as code shows the saved zone as generated DNSControl-compatible JavaScript; unsaved editor changes are not included. The same tab can load a matching D("zone") back into the editor for review before Save changes.

Replace preview and push#

DNSControl’s command loop:

bash
$ dnscontrol preview
$ dnscontrol push

becomes this app loop:

Edit the saved draft

Use Records, Settings, or Config as code, then choose Save changes.

Build a live plan

Choose Preview changes. DNSMigrator reads every attached provider and opens Changes.

Review every provider

Expand each provider plan and inspect its Add, Change, and Delete rows, unchanged count, unsupported records, and provider errors.

Confirm the push

Type the zone name and choose Push to N providers. The worker re-reads each provider, calculates a fresh plan, and creates a pre-push backup before provider writes.

NO_PURGE maps to Never delete records at the provider. With normal purge behavior, a managed zone can delete live records not in the draft. Push requires the saved draft version to match the preview and a typed zone-name confirmation. The worker then re-reads each provider, applies its newly calculated plan, and stores a pre-push backup. Read Preview and push before the first production push.

For a provider-to-provider move rather than ongoing desired-state management, use Quickstart: your first migration. Migration Keep extra records is the one-time counterpart to merge behavior; Make an exact copy allows destination-only deletions.

Replace get-zones and check-creds#

To bootstrap from a provider, choose Zones → Add zone → Import from a provider, then select the connection and visible zone. DNSMigrator reads the live records into a saved draft and excludes provider-managed SOA and apex NS records. You can also choose a connected source under New migration without first making it a managed zone.

Creating a connection performs the access check that check-creds provided. Use Check access later to run it again. The row records whether the adapter confirmed read or read-and-write scope; a provider error is shown as Access failed.

The credential-free CLI does not implement provider get-zones because it never accepts provider credentials. It can normalize or convert an existing BIND export with dnsmigrator export.

Functions that carry across#

The browser runtime supports the core domain declarations, standard record functions, the provider-specific functions marked supported in the parity reference, ignore rules, NO_PURGE, nameserver declarations, DNSSEC intent, reverse-zone helpers, import transforms, and the SPF, DMARC, CAA, DKIM, Microsoft 365, and LOC builders. The zone editor exposes the same builders under Records → Use a builder.

Use the DNSControl parity reference for a row-by-row inventory. Important mappings include:

  • IGNORE, IGNORE_NAME, and IGNORE_TARGET → Settings → Ignore rules.
  • IGNORE_EXTERNAL_DNS → Leave Kubernetes external-dns records alone.
  • DISABLE_IGNORE_SAFETY_CHECK → Turn off the ignore safety check.
  • NO_PURGE and PURGE → Never delete records at the provider on or off.
  • AUTODNSSEC_ON and AUTODNSSEC_OFF → DNSSEC → Sign or Unsign.
  • NAMESERVER and NAMESERVER_TTL → extra nameservers and the saved config model.
  • DnsProvider(provider, count) → attached providers and Nameservers used.
  • IMPORT_TRANSFORM and IMPORT_TRANSFORM_STRIP → the managed-zone transform model.

Known deviations#

  • CLI_DEFAULTS records a warning for variables that were not supplied. The current import screen has no variable-entry control, so resolve those values in the script first.
  • SPF builder syntax imports, but SPF flattening needs DNS lookups. The browser importer has no resolver and leaves that record unflattened with a warning. The in-app SPF builder can resolve includes when you run it from the managed-zone editor.
  • Glob matching follows DNSControl’s implementation where it differs from older prose examples.
  • The DMARC and CAA builders validate policy, URI, tag, flag and range inputs instead of preserving invalid builder input.
  • IGNORE_NAME_DISABLE_SAFETY_CHECK is rejected; use the domain-wide DISABLE_IGNORE_SAFETY_CHECK form.
  • Deprecated AUTODNSSEC is a no-op with a warning; use the explicit _ON or _OFF form.
  • Provider metadata with no canonical equivalent is reported and dropped. Several Cloudflare domain-level settings can be preserved as config metadata but are not applied as provider operations.
  • Export warns when an exact DNSControl representation is unavailable. Non-weighted routing metadata is dropped, obsolete SPF records export as TXT, unsupported types become comments, and Hurricane Electric DDNS keys are omitted unless a caller explicitly requests secret export. The web UI does not request it.
  • Names are normalized to lowercase but are not converted to IDNA; use ASCII or already-punycode names.

Review all import and export warnings. Passing the parser means the JavaScript could be converted; it does not mean every attached provider can represent every record. Run Preview changes to get the provider-specific answer.

DNSControl migration questions#

Can I import a DNSControl dnsconfig.js file?
Yes. The browser importer evaluates the supported DSL in a sandboxed Web Worker and turns each D() declaration into a managed-zone draft. Read every warning because valid JavaScript does not guarantee that each destination provider supports every resulting record.
Should I upload DNSControl creds.json?
No. Recreate each account as an encrypted Connection, then map imported DnsProvider keys to those connections. DNSMigrator does not import or export provider secrets in dnsconfig.js.
What replaces dnscontrol preview and push?
Select Preview changes to read every attached provider and build a provider-specific plan. After reviewing it, type the zone name and use the push form; DNSMigrator recomputes the plan and takes a pre-push backup before sequential provider writes.
What replaces dnscontrol get-zones?
Use Zones → Add zone → Import from a provider to create a managed draft, or choose a connected provider directly in New migration. The credential-free CLI does not implement get-zones because it never accepts provider credentials.
Can the browser importer use require or fetch?
No. The sandbox has no filesystem or network access, so require, glob helpers, FETCH, and fetch are unavailable. Put required declarations and materialized data into the reviewed script before import.