Skip to content
DMNO
✨ If you've tried DMNO or looked through the docs, let us know what you think!

Using DMNO with Cloudflare

At DMNO we love Cloudflare. This very site is hosted on it! That’s why we’re excited to make it easier and safer to manage config and secrets in all of your Cloudflare projects.

This platform integration exposes premade schemas and underlying types to interact with env vars related to Cloudflare, as well as a special cli wrapper around wrangler that helps deal with config. Aside from all the usual benefits of DMNO - validation, type-safety, sync with backends like 1Password, sharing config across a monorepo - our Cloudflare integration has a few extra tricks up its sleeve:

  • configure wrangler using DMNO, injecting built-in env vars, and special new ones that are passed in via flags
  • inject config into Cloudflare Workers - during both local dev and deployment
  • enable DMNO security features in workers - leak prevention, log redaction
  • handle both static and dynamic config
  • add type-safety to your config, without needing to run wrangler types
  • access your config anywhere, not just within route handlers

Setup

Terminal window
npm add @dmno/cloudflare-platform

Configuring wrangler using DMNO

Many facets of the wrangler CLI, including authentication, can be set using system environment variables. By default, if you have a .env file in your repo, wrangler will automatically use it. But, this suffers from all the usual headaches of using a .env file. Instead, DMNO provides a premade vendor schema and associated types so you can inject validated settings into wrangler without resorting to a gitignored .env file. Instead, you can pull your sensitive Cloudflare API keys from any DMNO plugin, like 1Password or an encrypted vault, share values across a monorepo, or compose your config however you see fit.

Because wrangler is configured using a mix of wrangler.toml, env vars, CLI flags, and has no concept of plugins, we also provide a wrapper CLI, called dwrangler, that handles everything automatically.

For example, if you wanted to pull your Cloudflare credentials from 1Password, configure dev options, and inject your config, your .dmno/config.mts might look like this:

.dmno/config.mts
import { CloudflareWranglerEnvSchema, DmnoWranglerEnvSchema } from '@dmno/cloudflare-platform';
import { OnePasswordDmnoPlugin } from '@dmno/1password-plugin';
import { DmnoBaseTypes, defineDmnoService, pickFromSchemaObject, switchBy } from 'dmno';
// initialize our 1Password plugin
const opSecrets = new OnePasswordDmnoPlugin('1pass', {
fallbackToCliBasedAuth: true,
});
export default defineDmnoService({
schema: {
// config that affects wrangler directly
...pickFromSchemaObject(CloudflareWranglerEnvSchema, {
CLOUDFLARE_ACCOUNT_ID: {
value: opSecrets.itemByReference('op://Shared/Cloudflare/account id'),
},
CLOUDFLARE_API_TOKEN: {
value: opSecrets.itemByReference('op://Shared/Cloudflare/workers api token'),
},
}),
// special config that controls wrangler via `dwrangler` cli wrapper (all optional)
...pickFromSchemaObject(DmnoWranglerEnvSchema, {
WRANGLER_ENV: {}, // passed as --env
WRANGLER_DEV_IP: { value: 'custom.host.local' }, // passed as --ip
WRANGLER_DEV_PORT: { value: 8881 }, // passed as --port
WRANGLER_DEV_URL: {}, // will be populated with full dev URL
WRANGLER_LIVE_RELOAD: { value: true }, // passed as `--live-reload`
WRANGLER_DEV_ACTIVE: {}, // true when running `dwrangler dev` or `dwrangler pages dev`
WRANGLER_BUILD_ACTIVE: {}, // true when dwrangler is performing a build for deployment
}),
// ... rest of your app config
SOME_VAR: {
value: switchBy('WRANGLER_DEV_ACTIVE', { // use info from wrangler to affect other config
_default: 'dev value',
false: 'prod value',
}),
},
},
});

To take advantage of this new config, you swap usage of wrangler to dwrangler, whether calling it directly or in your package.json scripts.

package.json
{
"scripts": {
"dev": "dwrangler dev",
"deploy": "dwrangler deploy"
}
}

Cloudflare Workers

Dealing with config in Cloudflare Workers is a bit different than other JS/TS environments. Instead of relying on a global process.env, config is passed in as bindings to route handlers. These values can be either vars (not-sensitive, plaintext) or secrets (sensitive, encrypted), and can be set in a variety of ways for local development and for deployments:

  • wrangler --var CLI option - sets vars during local dev, also sets non-sensitive vars during wrangler deploy
  • wrangler.toml in a [vars] section - sets vars, can also be varied per environment (e.g., [staging.vars])
  • .dev.vars file - set sensitive secrets during local development only
  • wrangler secret - command to set secrets remotely, also see wrangler versions secret to handle versioned deploys with secrets

Additionally, if not relying on a custom build, wrangler internally uses esbuild, and you can do build-time replacements, which can also be used for configuration purposes:

  • wrangler.toml in a [define] section - static vars, can also be varied per environment (e.g., [staging.define])
  • wrangler --define CLI option - does static replacements during the build, both locally and during deploys

Navigating all of this can be tricky, so we built this integration to make it as easy as possible. Define your DMNO config, and we take care of the rest, giving you a unified way to access your configuration, regardless of if it is static or dynamic, and sensitive or not.

Injection via inline mode

While it is not very Cloudflare-y, our preferred way to inject DMNO config is by inlining the entire resolved config during the build/deploy process. While it may feel a little odd, config changes always trigger a new deployment anyway.

Doing it this way has a few important benefits:

  • validated and coerced config, with additional metadata, and it is directly a part of a specific version of your worker
  • all config can now be accessed everywhere, just like process.env, not just within request handlers
  • type-safety and IntelliSense on your config, without re-running wrangler types
  • enables DMNO’s security features to prevent secrets from leaking over http responses, requests to other servers, and redact secrets from logs

To set this up, we must import the DMNO globals injector at the top of your main worker entrypoint. This version is specifically made to be compatible with Cloudflare’s edge runtime, and will look for data injected by dwrangler at build time.

your-worker.js
import 'dmno/injector-standalone/edge-auto';
console.log(DMNO_CONFIG.SOME_ITEM); // 🎉 config is now available everywhere!
export default {
async fetch(request, env, ctx) {
return new Response(`API host: ${DMNO_CONFIG.API_HOST}`);
},
};

Injection via secrets mode

If you want to continue to use Cloudflare’s built-in secrets functionality, you can instead use the secrets injection mode. In this mode, static config items will still be replaced at build time, dynamic config items will be set as Cloudflare secrets and you will continue to read them from the env binding injected into your route handlers.

To enable this mode, you must set the WRANGLER_INJECT_MODE in your config to secrets:

.dmno/config.mts
export default defineDmnoService({
schema: {
...pickFromSchemaObject(DmnoWranglerEnvSchema, {
WRANGLER_INJECT_MODE: { value: 'secrets' },
}),
},
});

Pros:

  • secrets are no longer bundled into your code
  • existing calls to env.SOME_KEY will continue to work

Cons:

  • we cannot activate DMNO’s security features - sensitive secrets can appear in logs, and easily be leaked
  • you still cannot access config outside of route handlers
  • secrets will always be injected as strings
  • less reliable types

To make things a bit more convenient, we do replace references to DMNO_CONFIG.SOME_VAR to env.SOME_VAR, which means you get automatic type-safety and IntelliSense, and you never have to think about whether a config item is static or dynamic.

While it should all feel seamless, running dwrangler deploy in this mode calls multiple commands to do a 3-part deployment:

  1. build and upload a new versioned deploy, without activating it
  2. upload resolved config as encrypted secrets, creating another version with secrets attached
  3. activate the new version with correct secrets attached

Which mode should you use?

We think for most users the inline mode is the better choice, but both options have their merits. Here is a comparison table to help:

Inline modeSecrets mode
👍 Static config injected at build time, accessible anywhere< same
✨ Dynamic config acessible outside route handlers!😪 Dynamic config only accessible within route handlers
must use env naming convention
🔐 Log redaction, leak detection!😢 No DMNO security features
🙊 Bundled worker code is accessible within the Cloudflare UI🙈 Cloudflare secrets are never visible within the Cloudflare UI
🛠️ Additional installation code🛠️ More complex deployment

Using dwrangler with the Workers Builds git integration

Cloudflare’s new Workers Builds (beta) allows you to connect your worker to a git repo, and it will run the CI process within Cloudflare, similar to how it works for Pages.

While this is great, it’s not much different than running your CI anywhere else, and the same issues of injecting config into your running worker are present.

To use dmno within Workers Builds, just swap your deploy command from wrangler to dwrangler, just like if you were running it anywhere else. There is also a section for setting “Build variables and secrets” where you can populate any additional config needed during the build. If you are using DMNO to fetch sensitive config from somehwere else, this is how you would pass in that secret-zero, for example, a service account token for 1Password.

Cloudflare Pages

Cloudflare pages allows you to host static sites and provides a little sugar on top of Cloudflare Workers for attached functions. Unfortunately, Wrangler’s Pages functionality does not allow the same level of configuration of the underlying ESBuild process. However, it may not matter because, in practice, most users are likely using an existing framework and already have their own build process. In this case, you can rely on our drop-in integrations to inject your DMNO config.

Regardless, you could still use DMNO to resolve your config and upload secrets to Cloudflare, but without a custom build, you would have to rely on env rather than DMNO_CONFIG, and you would not get all the benefits of DMNO.

Please reach out if you need help setting things up, or have a use case that is not supported.

Cloudflare Pages env vars

The Cloudflare Pages environment injects a few environment variables into its build environment that provide information about the current build. This module exposes a pre-made config schema object which you can use in your own schema. You can use the pickFromSchemaObject utility to pick only the env var keys that you need from the full list. For example:

.dmno/config.mts
import { defineDmnoService, switchBy, pickFromSchemaObject } from 'dmno';
import { CloudflarePagesEnvSchema } from '@dmno/cloudflare-platform';
export default defineDmnoService({
schema: {
...pickFromSchemaObject(CloudflarePagesEnvSchema, 'CONTEXT', 'BUILD_ID'),
APP_ENV: {
value: switchBy('CONTEXT', {
_default: 'local',
'deploy-preview': 'staging',
'branch-deploy': 'staging',
production: 'production',
}),
},
},
});

Other workflows

If you have a totally custom setup, that does not fit with the above workflows, you can still use dwrangler to manage Cloudflare auth, and push resolved config to Cloudflare however you want.

Note that wrangler has several bulk secret related methods, and they all take JSON from stdin.

For example: dwrangler secrets:bulk < dmno resolve --format json