Pricing

How to Deploy Your First Service with Zero-Config CI/CD

Planton Team
service-hubci-cdbuildpacksgetting-startedpipelinekustomize

This tutorial takes you from a GitHub repository containing application code to a running deployment on Kubernetes. You will not write a Dockerfile. You will not author pipeline YAML. You will not configure a CI/CD system. Planton handles all of that.

You will do two things: tell Planton where your code lives and where you want it deployed. The platform auto-detects your application's language using Cloud Native Buildpacks, builds a container image, and deploys it to Kubernetes through a fully managed pipeline. When you push code to your default branch, the entire cycle repeats automatically.

Note: The Planton web console provides a guided creation wizard for Services. This tutorial uses the CLI/YAML approach for stability and reproducibility. The console UI evolves frequently -- always check it for the latest experience.

A note on field casing: Planton manifests accept both camelCase and snake_case field names (standard protobuf JSON mapping). This tutorial uses camelCase, matching the convention used by Planton presets and ops manifests. Either style works.

What You Will Learn

  • Create a Service resource that links your GitHub repository to Planton's CI/CD system
  • Structure kustomize overlays that define where and how your application deploys
  • Monitor a platform-managed pipeline from image build through deployment
  • Verify your deployed service is running on Kubernetes
  • Trigger a new deployment by pushing a code change

Prerequisites

How It Works

A Planton Service connects your Git repository to a CI/CD pipeline that builds container images and deploys them to Kubernetes using kustomize overlays. You define the Service in a YAML manifest pointing to your repo, and Planton handles the rest -- building on every push, exporting manifests, and applying them to your target cluster. For more on how Services, pipelines, and kustomize overlays work together, see the CI/CD documentation.

Step 1: Prepare Your Repository

Your application repository needs a _kustomize/ directory that tells Planton how to deploy your service. You can scaffold this automatically with the CLI or create it by hand.

Option A: Scaffold with the CLI

If you have the planton CLI and are inside your Git repository, run:

planton service kustomize init --envs dev

It writes one empty overlay per environment you name, plus the merge schema:

_kustomize/
├── planton-schema.json
└── overlays/
    └── dev/
        └── kustomization.yaml

Add your service's manifest (a KubernetesDeployment) to the overlay and list it under the overlay's kustomization resources, as in Option B, before continuing to Step 2.

Option B: Create the Structure Manually

Create the following directory structure in your repository root:

_kustomize/
├── base/
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays/
    └── dev/
        ├── kustomization.yaml
        └── service.yaml

_kustomize/base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - service.yaml

_kustomize/base/service.yaml

This is a KubernetesDeployment manifest -- a Planton Resource that defines how your application runs on Kubernetes. The base contains configuration shared across all environments.

apiVersion: kubernetes.planton.dev/v1alpha1
kind: KubernetesDeployment
metadata:
  name: my-service
  org: your-org
spec:
  version: main
  container:
    app:
      image:
        repo: ghcr.io/your-github-org/your-repo
      resources:
        requests:
          cpu: 50m
          memory: 100Mi
        limits:
          cpu: 1000m
          memory: 1Gi
      ports:
        - name: rest
          containerPort: 8080
          networkProtocol: TCP
          appProtocol: http
          servicePort: 80
  availability:
    minReplicas: 1

A few things to note about this manifest:

  • image.repo is the container registry path where your built image will be stored. Set this to match the registry and path you configured in your container registry connection. The image tag is not specified here -- the pipeline automatically sets it to the Git commit SHA when deploying.
  • containerPort should match the port your application listens on. The example uses 8080; adjust to match your application.
  • resources sets the CPU and memory requests and limits for your container. Start with conservative values and adjust based on actual usage.

_kustomize/overlays/dev/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

patches:
  - path: service.yaml

_kustomize/overlays/dev/service.yaml

The overlay patches only what differs from the base. At minimum, it sets metadata.env to identify the target Planton environment:

apiVersion: kubernetes.planton.dev/v1alpha1
kind: KubernetesDeployment
metadata:
  name: my-service
  env: dev

The env field tells Planton which environment this deployment targets. The platform resolves the Kubernetes cluster and namespace from your environment's configuration -- you do not need to specify a target cluster in the manifest.

Verify Locally

Before proceeding, confirm that your kustomize structure is valid:

kubectl kustomize _kustomize/overlays/dev/

This command merges the overlay with the base and prints the resulting manifest. Verify that the output contains your base configuration with metadata.env: dev applied.

Commit and push the _kustomize/ directory to your repository.

Step 2: Create the Service Manifest

Create a file named service.yaml (this file lives outside your application repository -- it is a Planton API resource, not part of your application code):

apiVersion: service-hub.planton.ai/v1alpha1
kind: Service
metadata:
  name: my-service
  org: your-org
spec:
  description: My first backend service on Planton
  gitRepo:
    gitConnection: your-github-connection
    ownerName: your-github-org
    name: your-repo
    defaultBranch: main
  build:
    buildpacks: {}
    registry: your-registry-connection
    imageRepositoryPath: your-github-org/your-repo
    triggers:
      branches:
        - main
  deploy:
    kustomize: {}

Replace the placeholder values with your actual configuration:

FieldWhat to setWhere to find it
metadata.nameA name for your service (lowercase, hyphens allowed)You choose this
metadata.orgYour Planton organization slugplanton get organization
gitRepo.gitConnectionThe slug of your GitHub connectionplanton get github-connection
gitRepo.ownerNameYour GitHub organization or usernameYour GitHub account
gitRepo.nameYour repository nameYour GitHub repository
build.registryThe slug of your container registry connectionplanton get container-registry-connection
build.imageRepositoryPathThe path within your registry where images are pushedMust match image.repo in your kustomize base

Here is what each section does:

gitRepo: Links this Service to your GitHub repository. gitConnection is the slug of the GitHub connection you created in the GitHub connection tutorial. Planton uses it to read your code at each built commit and to report every run's verdict back to GitHub as a check on the commit.

build: How the service is built. Exactly one builder is set:

  • buildpacks: {} means Planton's release-pinned Buildpacks track detects your language and builds a container image with Cloud Native Buildpacks. No Dockerfile, no pipeline YAML.
  • dockerfile: {} is the alternative when you have a Dockerfile (see below); tektonPipeline: {} is for a pipeline you write yourself.
  • registry and imageRepositoryPath say where the image is pushed: <registry host>/<imageRepositoryPath>:<commit sha>.
  • triggers.branches lists the branches whose pushes build. Leaving it empty means the repository's default branch. Every trigger rule — branches, paths, pull requests, tags — lives here and nowhere else.

deploy.kustomize: {}: Declares that your repository's _kustomize/ tree is what the service deploys. The overlays under _kustomize/overlays/ ARE the environment set — a dev overlay means the service deploys to dev; add a production overlay later and it deploys there too, in your organization's promotion order. There is no separate list of environments to keep in sync.

Step 3: Apply the Service and Watch the Initial Pipeline

Deploy the Service:

planton apply -f service.yaml

Your GitHub connection's App installation already delivers push events for every repository it covers, so nothing needs to be registered on the repository. Start the first run yourself, on the head of your default branch:

planton service run my-service --branch main --follow

--follow streams the run's progress to your terminal. You will see the build stage's tasks execute:

  1. git-checkout: Clones your repository at the built commit
  2. build-image: Buildpacks detects your language, compiles your application, and pushes a container image tagged with the commit SHA
  3. kustomize-build: Renders each overlay against the base and stores the result for the deploy stage

Then the deploy stage walks your environments in promotion order — here, dev — applying the rendered manifests with the built image reference injected.

To find a run later, or to read a build's own output (the place a build failure explains itself):

planton service last-pipeline my-service        # the most recent run
planton service runs my-service                 # everything that ran, newest first
planton service logs <run-id>                   # the build's logs, every task's lines

Step 4: Verify the Deployment

Once the run completes, every environment it deployed has a deployment record — an immutable receipt with the exact artifact, the applied manifests, the URLs, and the rollout verdict:

planton service deployments my-service      # every deployment, newest first, with commit and artifact
planton service urls my-service             # where the service answers, per environment

Step 5: Push a Code Change and Watch the Run

Make a change to your application code, commit it, and push to a branch in build.triggers.branches:

git add .
git commit -m "update: my first change"
git push origin main

The push births a run. Find and follow it:

planton service last-pipeline my-service
planton service follow <run-id>

The new run builds a fresh container image tagged with the new commit SHA and deploys it to dev. Each push to main produces a new deployment with a traceable image tag, and the commit on GitHub carries a check named after the service with the run's verdict.

Common Patterns and Tips

Switching from Buildpacks to Dockerfile

If your application needs a custom build process, switch the builder to a Dockerfile:

spec:
  build:
    dockerfile:
      dockerfilePath: Dockerfile    # relative to the build context; defaults to "Dockerfile"
    registry: your-registry-connection
    imageRepositoryPath: your-github-org/your-repo

Everything else -- the compiled pipeline, the kustomize structure, the deployment flow -- remains the same. If you later need steps neither track offers (tests, scans, a multi-stage build), tektonPipeline: {} lets you write the pipeline yourself; see Self-Managed Pipelines.

Monorepo Support

If your service lives in a subdirectory of a monorepo:

spec:
  gitRepo:
    gitConnection: your-github-connection
    ownerName: your-github-org
    name: your-monorepo
    defaultBranch: main
    projectRoot: services/my-service
    sparseCheckoutDirectories:
      - services/my-service
  build:
    buildpacks: {}
    registry: your-registry-connection
    imageRepositoryPath: your-github-org/my-service
    triggers:
      paths:
        - "packages/shared-lib/**"
  • gitRepo.projectRoot is where the build runs and where _kustomize/ is found; a change under it always triggers
  • build.triggers.paths adds paths OUTSIDE the project root whose changes should also trigger (a shared library)
  • gitRepo.sparseCheckoutDirectories speeds up the clone by fetching only the directories the build needs

See Monorepo Support for the full model, including a Docker build context wider than the project root.

Enabling Ingress

To expose your service externally, add ingress configuration to your kustomize overlay:

# _kustomize/overlays/dev/service.yaml
apiVersion: kubernetes.planton.dev/v1alpha1
kind: KubernetesDeployment
metadata:
  name: my-service
  env: dev
spec:
  ingress:
    enabled: true
    hostname: my-service-dev.your-domain.com
  container:
    app:
      ports:
        - name: rest
          containerPort: 8080
          networkProtocol: TCP
          appProtocol: http
          servicePort: 80
          isIngressPort: true

Note the isIngressPort: true flag on the port -- this tells the ingress controller which port to route traffic to.

Adding a Production Environment

Create _kustomize/overlays/production/kustomization.yaml and _kustomize/overlays/production/service.yaml with production-specific settings (higher resource limits, more replicas), commit, and push. That push is a run: the platform reads the new overlay, records production as one of the service's environments, and every run from then on walks dev and then production in your organization's promotion order. There is no list on the Service to update — the overlays are the environment set. To pause a run before production for a human decision, mark the environment protected in your organization; see How to Configure Branch Deployments and Tag Releases.

Overriding the Kustomize Directory

By default the tree is _kustomize/ under projectRoot. If it lives elsewhere:

spec:
  deploy:
    kustomize:
      baseDirectory: deploy/kustomize    # relative to projectRoot

What to Do Next

Next article

How to Store and Reference Credentials in a Provider Connection

Planton provider connections never contain raw credentials. Instead, sensitive values like access keys are stored as secrets and non-sensitive shared values like regions are stored as variables -- and your connection manifest references them by slug. This tutorial walks you through the complete workflow: storing credentials, creating a variable, wiring both into an AWS provider connection manifest, applying it, and rotating a credential when it changes. Note: If you have already completed the...
Read next article