Skip to content

Tutorial

Warning

Make sure you have completed the steps in the setup before continuing.

In this tutorial you will see how to write a build file for a simple golang app.

Project setup

First, initialize a go module to have something to build

$ go mod init github.com/demo/typebuild

And create a main.go file

package main

import "fmt"

func main() {
    fmt.Println("Hello typebuild!")
}

Building

The build file can reside anywhere inside your repository, create a build directory:

$ mkdir build
$ cd build

And now, create the build file, call it build.ts:

$ touch build.ts

The first line of a build file must be //syntax=rumpl/typebuild, this tells buildkit where to look for a frontend that knows how to build an image from your typescript code.

Next, import the Image and BindMount classes

import { Image, BindMount } from "https://raw.githubusercontent.com/rumpl/typebuild-node/main/index.ts";

An Image represents a stage in a multi-stage Dockerfile, with it you can execute the instructions needed to create your image.

Create a stage with golang:1.18.0-alpine3.15 as base to build the app:

const builder = new Image("golang:1.18.0-alpine3.15");

You can now build your app:

builder
  .run("apk add git")
  .workdir("/app")
  .run("go build -o /binary", [new BindMount({ target: "." })]);

Since the build file uses golang 1.18 you need to install git or the build fails, this is a bug/feature from golang 1.18 and has nothing to do with typebuild, ask Sebastiaan, he knows.

First we're telling typebuild that we want to set the working directory to /app. Next comes the build, the first argument is the command to run, here go build with some flags. The second argument tells buildkit to mount the build context which contains the code of your app to the current working directory. For more information about run mounts you can read the buildkit documentation.

Since you're a good cloud native citizen you want to create a minimal image for the app. To do this create a new stage that holds only what's necessary for the app to run, which in this case is only the binary.

Start by creating a stage from scratch:

const final = new Scratch();

And now copy the binary from the builder stage to the final stage:

final.copy({
  from: builder,
  source: "/binary",
  destination: "/app",
});

And finally, set the entrypoint of the image:

final.entrypoint(["/app"]);

All that's left to do is export the final stage so that typebuild can see it:

export default final;

The final step is to build the image and test it:

$ docker buildx build -f ./build/build.ts -t hello-typebuild --load .
[+] Building 9.2s (17/17) FINISHED
 => [internal] load build definition from build.ts                                                0.1s
 => => transferring dockerfile: 435B                                                              0.0s
 => [internal] load .dockerignore                                                                 0.0s
 => => transferring context: 2B                                                                   0.0s
 => resolve image config for docker.io/rumpl/typebuild:latest                                     1.4s
 => [auth] rumpl/typebuild:pull token for registry-1.docker.io                                    0.0s
 => CACHED docker-image://docker.io/rumpl/typebuild@sha256:8ef375d817c6e5e0e15c12c8d682750a30e94  0.0s
 => => resolve docker.io/rumpl/typebuild@sha256:8ef375d817c6e5e0e15c12c8d682750a30e9488b696c3a82  0.0s
 => [typebuild] load typebuild from build.ts build.ts                                             0.1s
 => => transferring dockerfile: 514B                                                              0.0s
 => local://context                                                                               0.3s
 => => transferring context: 37.73kB                                                              0.2s
 => resolve image config for docker.io/library/golang:1.18.0-alpine3.15                           0.9s
 => [auth] library/golang:pull token for registry-1.docker.io                                     0.0s
 => docker-image://docker.io/library/golang:1.18.0-alpine3.15                                     0.2s
 => => resolve docker.io/library/golang:1.18.0-alpine3.15                                         0.2s
 => local://context                                                                               0.0s
 => CACHED apk add git                                                                            0.0s
 => CACHED mkdir /app                                                                             0.0s
 => go build -o /binary                                                                           1.7s
 => CACHED copy /binary /app                                                                      0.0s
 => exporting to oci image format                                                                 0.6s
 => => exporting layers                                                                           0.0s
 => => exporting manifest sha256:85c5a88cc4c5b3f4e36b06b2bffb2a7dfe33639c2dfb36f3ace8ce812bca5e2  0.1s
 => => exporting config sha256:5998e423b00f969f0f756aff10aa097a14dd2b26028d94cf0d0abb6ad460fbc7   0.0s
 => => sending tarball                                                                            0.5s
 => importing to docker                                                                           0.0s

You can then run the image

$ docker run --rm hello-typebuild
Hello typebuild

Hooray 🎉, you have successfully built your first image using typebuild.

Complete build file

The complete build file should look something like this

//syntax=rumpl/typebuild

import { BindMount, Image, Scratch } from "https://raw.githubusercontent.com/rumpl/typebuild-node/main/index.ts";

const golang = new Image("golang:1.18.0-alpine3.15")
  .run("apk add git")
  .workdir("/app")
  .run("go build -o /binary", [new BindMount({ target: "." })]);

const final = new Scratch()
  .copy({
    from: golang,
    source: "/binary",
    destination: "/app",
  })
  .entrypoint(["/app"]);

export default final;