Dockerfiles are a constant source of frustration in the container ecosystem. First, they are difficult to write (well). They can't express all types of build graphs – only linear graphs are easily expressible, limiting the amount of parallelism one can do (i.e., how fast your builds can be). Finally, they are difficult to natively integrate into build systems – passing well-known build arguments, environment variables, and other build-time variables.

But what if we could easily define docker builds in code? A high-level description of the solution, then a blueprint for how it should be done.

Solution: Typescript Docker Construct

Define a DAG (directed acyclic graph) using the same construct pattern as AWS CDK, Pulumi, and Terraform use for infrastructure. Serialize the synthesized construct to a Buildkit frontend that transparently executes the operations using docker build without any other plugins or arguments (see: An Alternative to the Dockerfile).

export class Build extends Construct {
    constructor(props = {}) {
        super(undefined as any, '');

        const buildImg = new Image(this, 'buildImage', {
            from: 'ubuntu:latest',
            buildArgs: {
                'http.proxy': 'http://proxy.example.com:8080',
                'https.proxy': 'https://proxy.example.com:8080',
            },
        });

        const appArtifacts = new AppBuilder(this, 'appBuild', {
            image: buildImg,
            source: new Source(this, 'gitSrc', {
                url: 'git://github.com/moby/buildkit.git',
            }),
        });

        new MergeOp(this, 'merge', {
            inputs: [
                new SourceOp(this, 'src1', {
                    source: appArtifacts.outputs.image,
                    exec: {
                        path: './bin/app',
                        args: ['--arg1', '--arg2'],
                    },
                }),    
        });

        const runtimeImage = new Image(this, 'runtimeImage', {
            from: buildImg,
            buildArgs: {
                'NODE_ENV': 'production',
            },
        });

        runtimeImage.copy(this, 'copy', {
            source: appArtifacts.outputs.image,
            destination: '/app',
        });
    }
}

Why?

How?

If you're interested in working on this, let me know. I'm happy to provide more guidance or answer any questions on the specifics of how it would work.