Skip to content

Building Modules

This guide covers building TinySystems modules for production deployment.

Container Build

Dockerfile

Standard multi-stage Dockerfile:

dockerfile
# Build stage
FROM golang:1.21-alpine AS builder

RUN apk add --no-cache git ca-certificates

WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o module ./cmd/main.go

# Runtime stage
FROM alpine:3.18

RUN apk add --no-cache ca-certificates tzdata

WORKDIR /app

COPY --from=builder /app/module .

# Non-root user
RUN adduser -D -u 1000 appuser
USER appuser

ENTRYPOINT ["/app/module"]

Build Commands

bash
# Local build
docker build -t my-module:dev .

# With build args
docker build \
  --build-arg VERSION=1.0.0 \
  --build-arg COMMIT=$(git rev-parse HEAD) \
  -t my-module:1.0.0 .

# Multi-platform
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry.io/my-module:1.0.0 \
  --push .

Using CLI

bash
# Simple build
tinysystems build --tag my-module:1.0.0

# Build and push
tinysystems build --tag registry.io/my-module:1.0.0 --push

# Multi-platform
tinysystems build \
  --tag registry.io/my-module:1.0.0 \
  --platform linux/amd64,linux/arm64 \
  --push

Build Optimizations

Layer Caching

Optimize for Docker layer cache:

dockerfile
# Cache go mod download
COPY go.mod go.sum ./
RUN go mod download

# Only copy source after mod download
COPY . .

Binary Size

Reduce binary size:

dockerfile
RUN CGO_ENABLED=0 GOOS=linux \
    go build -ldflags="-w -s -X main.version=${VERSION}" \
    -o module ./cmd/main.go
FlagEffect
-wRemove DWARF debug info
-sRemove symbol table
-XSet variable at link time

Multi-Stage Builds

Minimal runtime image:

dockerfile
# Use distroless for even smaller images
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/module /module
ENTRYPOINT ["/module"]

Version Injection

Build-Time Variables

go
// main.go
package main

var (
    version = "dev"
    commit  = "unknown"
    date    = "unknown"
)

func main() {
    log.Info("starting module",
        "version", version,
        "commit", commit,
        "date", date,
    )
    // ...
}

Build with Version

bash
go build -ldflags="-X main.version=1.0.0 -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o module ./cmd/main.go

In Dockerfile

dockerfile
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown

RUN go build -ldflags="-w -s \
    -X main.version=${VERSION} \
    -X main.commit=${COMMIT} \
    -X main.date=${BUILD_DATE}" \
    -o module ./cmd/main.go

CI/CD Build

GitHub Actions

yaml
name: Build and Push

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract version
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}
            ghcr.io/${{ github.repository }}:latest
          build-args: |
            VERSION=${{ steps.version.outputs.VERSION }}
            COMMIT=${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

GitLab CI

yaml
build:
  stage: build
  image: docker:24-dind
  services:
    - docker:dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker buildx create --use
    - docker buildx build
        --platform linux/amd64,linux/arm64
        --build-arg VERSION=$CI_COMMIT_TAG
        --build-arg COMMIT=$CI_COMMIT_SHA
        --push
        -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
        .
  only:
    - tags

Build Validation

Verify Image

bash
# Check image layers
docker history my-module:1.0.0

# Check image size
docker images my-module:1.0.0

# Run smoke test
docker run --rm my-module:1.0.0 --version

# Scan for vulnerabilities
trivy image my-module:1.0.0

Test Before Push

yaml
# In CI
- name: Test image
  run: |
    docker run -d --name test-module my-module:$VERSION
    sleep 5
    docker logs test-module
    docker exec test-module wget -q -O- http://localhost:8080/healthz
    docker stop test-module

Makefile

Common build tasks:

makefile
VERSION ?= $(shell git describe --tags --always --dirty)
COMMIT := $(shell git rev-parse HEAD)
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
REGISTRY ?= ghcr.io/myorg
IMAGE := $(REGISTRY)/my-module

.PHONY: build docker-build docker-push

build:
	go build -ldflags="-w -s \
		-X main.version=$(VERSION) \
		-X main.commit=$(COMMIT) \
		-X main.date=$(DATE)" \
		-o bin/module ./cmd/main.go

docker-build:
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg COMMIT=$(COMMIT) \
		--build-arg BUILD_DATE=$(DATE) \
		-t $(IMAGE):$(VERSION) .

docker-push: docker-build
	docker push $(IMAGE):$(VERSION)
	docker tag $(IMAGE):$(VERSION) $(IMAGE):latest
	docker push $(IMAGE):latest

docker-buildx:
	docker buildx build \
		--platform linux/amd64,linux/arm64 \
		--build-arg VERSION=$(VERSION) \
		--push \
		-t $(IMAGE):$(VERSION) .

Next Steps

Build flow-based applications on Kubernetes