jcloud · for Japode Cloud services

One manifest. Every file that has to agree with it.

jcloud reads a service's japode.yml and checks it against the Dockerfile, Caddyfile and workflows in its repository, and against the compose file, route, backup.conf and .env.example the platform deploys beside it. A port moved in one place and not the other fails on that commit, not on the deploy.

  • Linux · macOS · Windows
  • One Go binary
  • Runs in GitHub Actions

japode.yml, excerpt

version: 1
name: notes
image: ghcr.io/example/notes
port: 8080
health:
  path: /healthz
  probe: curl
  start_period: 10s
volumes:
  - name: data
    path: /data
    sqlite: [notes.db]
routes:
  - hosts: [notes.example.com]
    max_body: 2MB

terminal

$ jcloud check japode.yml
  ✓ japode.yml: notes states what it declares (1 volume(s), 1 route(s), 2 variable(s))

1 manifest(s) state what they declare
A real run on a demo manifest, copied from the terminal.

What it catches

The mistakes nothing else compares

docker compose config and caddy validate each read one file. A port written in three files passes both while the route aims at nothing. Each tab is one jcloud verb on a small demo service, with exactly what it printed.

A first draft

check reads one manifest and refuses what a service has to declare and does not. Here: no health path, a route with no address, and a variable that is both fixed and defaulted. Every problem is reported at once, so one run is enough.

  • japode.yml

demo/checkexit 1

$ jcloud check japode.yml
  ✗ japode.yml (.)
      health.path: is missing — the deploy waits on it and the route probes it
      routes[0].hosts: is missing — a route with no address reaches nothing
      env[0]: names both default: and value: — a fixed value is not configurable

1 of 1 manifest(s) refused

Every line above was printed by jcloud v0.3.0 (schema version 1, built with go1.26.8), run against the files in site/demo.

Where it runs

One manifest, three places that have to agree with it

The manifest sits in the service's repository and a copy sits in the platform's. jcloud runs in both, and against what the server reports.

  1. Service repository

    its CI, on every pull request

    • japode.yml
    • Dockerfile
    • Caddyfile
    • .github/workflows

    jcloud checkjcloud check --repo

  2. Platform repository

    its CI, and before a deploy

    • services/<name>/japode.yml
    • docker-compose.yml
    • caddy/sites/<name>.caddy
    • backup.conf
    • .env.example
    • .env.age

    jcloud agreejcloud comparejcloud upjcloud env

  3. Server

    read by a collector, never by jcloud

    • running containers
    • pinned digests

    jcloud survey

Install

One binary, three ways to get it

The current release is v0.4.0. Every way below installs that one.

Download a binary

Take the archive for your system from the release, check it against checksums.txt, and put jcloud on your PATH.

  • Linux · amd64, arm64
  • macOS · amd64, arm64
  • Windows · amd64, arm64

Each archive is listed with its SHA-256 in checksums.txt.

Install with Go

Any Go toolchain new enough for go.mod builds it from source.

go install github.com/Japode/[email protected]

Run it in GitHub Actions

Where jcloud earns its keep: in the service's own repository, so the commit that moves a port without the manifest fails in front of whoever made it. The step downloads the release, checks it, and runs check --repo.

.github/workflows/manifest.yml

name: manifest
on: [push, pull_request]
jobs:
  japode-yml:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: jcloud check --repo
        env:
          V: 0.4.0
        run: |
          A=jcloud_${V}_linux_amd64.tar.gz
          curl -fsSLO "https://github.com/Japode/jcloud/releases/download/v$V/$A"
          curl -fsSL "https://github.com/Japode/jcloud/releases/download/v$V/checksums.txt" | grep " $A\$" | sha256sum -c
          tar -xzf "$A" jcloud
          ./jcloud check --repo . --repository ${{ github.repository }} japode.yml