Deno for Infrastructure as Code

Why

Deno is a v8 runtime built in Rust which makes it easy to write performant and secure javascript/typescript apps. It has great developer tooling with a single cli(deno) containing the:

So all you need to start a simple one file project with Deno, is the deno cli. But what’s exciting is that even for complex projects, that’s all you would ever need ! The deno cli also comes with sane defaults so for the most part one doesn’t need a configuration file to manage the aforementioned tooling.

Incidentally, many Deno features and design decisions are also a good match for using it to write infrastructure code. It allows you to write secure and reproducible code with a developer friendly interface in a succint way. Let’s walkthrough a simple example to see these aspects:

We have a handlebarsjs Dockerfile template where we want to replace the port variable using typescript in Deno.

Dockerfile.template

1
2
3
4
FROM alpine:3.8
EXPOSE {{port}}
COPY app /
ENTRYPOINT /app

main.js

1
2
3
4
import HandlebarsJS from "https://dev.jspm.io/handlebars@4.7.6";
const compile = (HandlebarsJS).compile;
const template = compile(Deno.readTextFileSync("./Dockerfile.template"));
console.log(template({ port: 80 }));

Secure by default

Let’s run the above script with the deno cli

1
2
3
4
5
$ deno run main.js

error: Uncaught PermissionDenied: Requires read access to "./Dockerfile.template", run again with the --allow-read flag
const template = compile(Deno.readTextFileSync("./Dockerfile.template"))
...

deno refuses to execute the program since it doesn’t have permissions to read the Dockefile.template file.

Running it again with --allow-read permission fixes this problem.

1
2
3
4
5
$ deno run --allow-read main.js
FROM alpine:3.8
EXPOSE 80
COPY app /
ENTRYPOINT /app

We can clamp it down further by permitting deno to have read permissions only for the Dockerifle.template file

1
2
3
4
5
$ deno run --allow-read=Dockerfile.template main.js
FROM alpine:3.8
EXPOSE 80
COPY app /
ENTRYPOINT /app

allow-read takes a comma separated list of files.

So deno doesn’t have access to anything by default: network, disk, environment variables. It provides the following permissions to allow access:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ deno run --help 

...
        --allow-env=<allow-env>
            Allow environment access

        --allow-ffi=<allow-ffi>
            Allow loading dynamic libraries

        --allow-hrtime
            Allow high resolution time measurement

        --allow-net=<allow-net>
            Allow network access

        --allow-read=<allow-read>
            Allow file system read access

        --allow-run=<allow-run>
            Allow running subprocesses

        --allow-write=<allow-write>
            Allow file system write access

...

More on permissions here.. It also has a permissions module to do granular runtime permission checks.

Offline modules

But doesn’t the import fetch the handlebarjs module over the network ? Yes. That could be a problem.

Importing modules over the network in production might cause a number of problems:

Deno solves this problem by providing three features:

So for the above example, this strategy would look like this:

Create a lock file and commit it:

1
2
3
4
deno cache --lock=lock.json --lock-write main.js
git add -u lock.json
git commit -m "add lock file"
git push

On another machine, a collaborator can using this lock file to get verified modules:

1
2
deno cache --reload --lock=lock.json main.js
deno run --allow-read=Dockerfile.template main.js

We can take it further by doing a runtime verification too:

1
deno run --lock=lock.json --cached-only --allow-read=Dockerfile.template main.js

The above command verifies using the lock file and uses only cached modules with no remote fetching. Read more about this here.

Hyper Modularity

Writing and publishing small, focused yet versioned modules is a breeze. The above docker_template module is probably too small to publish as a npm module and probably not worth the investment. In Deno, since you can import any exported functions from a versioned file, one can simply do this:

1
2
// fake example
import { runDockerTemplate } from "https://raw.githubusercontent.com/myfakeorg/docker_template/v0.0.1/index.ts";

This can be of enourmous value for operators to easily publish small reusable modules.

Typescript

The out of the box typescript support sweetens the deal. If you are an operator trying to practice dev-ops(as in equitable participation of operators and developers in maintaining infrastructure on a build it, run it basis) and wants to provide a familiar and expressive frontend for your infrastructure tooling, typescript could be quite important. Especially if the majority of the engineers in your organisation use typescript most of the time.

Typescript allows you to write type-safe code and a first class autocompletion support in most IDEs/text editors.

Let’s rewrite the above simple example in typescript:

Define a module, docker_template.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import HandlebarsJS from "https://dev.jspm.io/handlebars@4.7.6";
const compile = (HandlebarsJS as any).compile;

export interface TemplateParams {
  port: number;
}

export const runDockerTemplate = (params: TemplateParams): string => {
  const port = params.port || 80;
  const template = compile(Deno.readTextFileSync("./Dockerfile.template"));
  return template({ port: port });
};

Use the module in main.ts

1
2
import { runDockerTemplate } from "./docker_template.ts";
console.log(runDockerTemplate({ port: 8080 }));

Run it,

1
deno run --lock=lock.json --cached-only --allow-read=Dockerfile.template main.ts

In the typescript implementation we are able to constrain port to a number type which also shows up a autocompletion in the editor. Trivial, but enhances the developer experience greatly.

autocompletion

Ecosystem and future

Deno has a great potential to become the a middle ground for developers and operators to work together on building and maintaining infrastructure. Providing a typed and familiar frontend to infrastrcuture can unlock the next generation of developer friendly tooling. This space is seeing some serious activity too: aws cdk, terraform cdk, pulumi to name a few. Deno has an additional related feature in terms of a simple, secure & performant runtime apart from the common offering of a typed and familiar language.

Takeaway

Personally I am betting on Deno(and typescript) to be one of the key tools in an infrastructure operator’s toolkit. What I like about it:

dxcfg: A configuration as code libary for Deno

The dxcfg project is my attempt to use Deno in production infrastructure. It' an opinionated port of jkcfg which has similar goals. In my current workplace, we use jkcfg to build a base typescript library which is used by developers to deploy applications in a standardised way. I have taken learnings from this experience and enhanced it with the power of Deno to try and build a type-safe, familiar and safe infrastructure toolkit.