Skip to content
Get Started

Get Started

Today, you bootstrap a new modular service with common-template-copier, GitLab’s standard project template. It scaffolds a three-layer service with LabKit v2 already wired in, a CI pipeline built on common-ci-tasks, and the Caproni and Runway configuration needed for local development and production deployment.

Coming soon: we are automating more of this workflow with bench, the CLI from the LabBench project. Until that lands, the copier flow below is the paved path.

Prerequisites

ToolPurposeInstall
copierProject template generatorpip3 install --upgrade copier
miseTask runner and tool versionscurl https://mise.run | sh

Everything else — Go, Caproni, Fairway, helm, kubectl, and the rest — is pinned in the generated project’s .tool-versions and installed for you by scripts/prepare-dev-env.sh, so every contributor gets the same versions.

Scaffold a new service

Run copier against the template and follow the prompts:

copier copy https://gitlab.com/gitlab-com/gl-infra/common-template-copier.git my-service --trust

The questions that matter for a modular service:

QuestionAnswer
project_nameLowercase project path slug (e.g. my-service)
gitlab_namespaceGroup path without the host (e.g. gitlab-org/ops)
initial_codeownersYour @gitlab_username
golangyes
release_platformyes — this selects the LabKit-based three-layer service
three_layer_goyes (the default) — scaffold the transport/domain/store example app

Answering golang + release_platform + three_layer_go gives you the modular service skeleton:

my-service/
├── main.go                 # LabKit v2 app + httpserver bootstrap
├── internal/
│   ├── transport/          # HTTP handlers and routes
│   ├── domain/             # Business logic
│   └── store/              # Data access layer
├── .gitlab-ci.yml          # CI/CD pipeline (via common-ci-tasks)
├── .runway/fairway.yaml    # Fairway chart configuration
├── caproni.yaml            # This project's local Kubernetes rig
├── caproni.infra.yaml      # Template-owned infra fragment (cert-manager, CNPG, dev CA)
├── caproni/                # Local-dev charts, values, and README
├── .tool-versions          # Pinned tool versions, installed via mise
├── .mise.toml              # Local task runner
├── go.mod
└── README.md

To see a finished, deployed example of this exact layout, lean on the Go service template exemplar — a complete Theseus service running in staging and production that you can read alongside this tutorial.

LabKit is baked in

The generated main.go bootstraps a LabKit v2 application, so every service starts with:

  • Structured logging in the format GitLab’s logging pipeline expects, enriched with build metadata (version, commit, date) injected at build time
  • HTTP server component with access logging, tracing, correlation IDs, panic recovery, health endpoints, and graceful shutdown handled for you
  • Three-layer architecture (transport → domain → store) enforced from day one, so every service looks the same and engineers can navigate any Theseus-managed service immediately

See the LabKit page for what the library layer provides.

Set up and push

From the generated project, initialise git before running the setup script — it installs pre-commit hooks, which need the repository to exist:

cd my-service
git init --initial-branch=main
scripts/prepare-dev-env.sh   # installs pinned tools via mise + git hooks
git add -A && git commit -m "Initial commit"
git remote add origin git@gitlab.com:gitlab-org/ops/my-service.git
git push --set-upstream origin main

The service builds and its tests pass before you write a line of domain code:

mise run test

Then register the project with infra-mgmt so that branch protections, approval rules, and CI/CD tokens are managed through automation.

Run locally with Caproni

The generated project pins Caproni 4 in .tool-versions and includes a caproni/README.md walkthrough. The first-time flow:

scripts/chart-gen.sh            # render chart/ from .runway/fairway.yaml via Fairway
caproni up                      # provision k8s, deploy GitLab + my-service
sudo caproni update-etc-hosts   # wire my-service.caproni.test into /etc/hosts
scripts/build.sh linux-arm64    # build the service binary (match your host arch)
scripts/container.sh            # bake the :local image the chart expects

Caproni provisions a local Kubernetes cluster and deploys your service’s Fairway-rendered chart alongside GitLab — the same images and charts used in production. Until container.sh has built the :local image, the service pod sits in ImagePullBackOff; that’s expected, not a bug. Edit mode (caproni run) then rebuilds and restarts the in-cluster process on file save. See caproni/README.md in your project and the Caproni docs for the full walkthrough.

Since Caproni 4.0, nothing is composed implicitly — everything the rig depends on is declared across three files:

  • caproni.yaml — this project’s cluster, repositories, deployers, and reloaders. Yours to edit.
  • caproni.infra.yaml — the infrastructure fragment (cert-manager, trust-manager, CloudNativePG, the dev CA). Template-owned: copier update keeps it current, so don’t edit it in place.
  • caproni.local.yaml — optional machine-specific overrides, merged last. Keep it out of version control. For example, if your existing Caproni VM is smaller than the requested size:
# caproni.local.yaml
version: 2
cluster:
  colima:
    memory: 8GiB

Deploy to production

.runway/fairway.yaml declares your service’s chart configuration, and the CI pipeline generated by the template handles building, signing, and releasing. Once the project is registered with the Release Platform, deployments flow through CI without manual intervention. See Runway for how binding and deployment work.

Keeping your service up to date

The template is versioned. Pull in template improvements at any time with:

copier update --trust --skip-answered

Your project’s current template version is recorded in .copier-answers.yml. See Update your service template for conflict handling and migrations.

What’s next

Last updated on