npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

alchemy-kubernetes-addons

v0.1.2

Published

Composable Kubernetes add-ons and safe primitives for Alchemy

Readme

alchemy-kubernetes-addons

Composable Kubernetes resources built on Alchemy's public cluster-adapter API. They work with any Kubernetes.ClusterLike, including Hetzner K3s, Docker K3s, EKS, and kubeconfig connections.

Add the provider beside Alchemy's Kubernetes provider:

providers: Layer.mergeAll(
  Kubernetes.providers(),
  KubernetesAddons.providers(),
),

Secrets

Secret converts literal and lazy Effect values to Effect Redacted, maps Config and Output values to Redacted before provider diffing and state persistence, unwraps them only in the Kubernetes PATCH request, and never returns Secret data from reads:

const credentials =
  yield *
  KubernetesAddons.Secret("Credentials", {
    cluster,
    namespace: "external-dns",
    name: "cloudflare-api-token",
    stringData: { "api-token": token.value },
  });

Use Kubernetes.Manifest only for public manifest data. Redacted desired inputs still exist in Alchemy state so updates can be detected; production stacks require encrypted remote state.

Ready Helm charts

ReadyHelmChart delegates rendering and ownership to Alchemy's existing Kubernetes.HelmChart, then waits with a bounded deadline for CRDs, Deployments, DaemonSets, StatefulSets, and Jobs:

const chart =
  yield *
  KubernetesAddons.ReadyHelmChart("Controller", {
    cluster,
    chart: "controller",
    repo: "https://charts.example.com",
    version: "1.2.3",
    namespace: "controller",
    timeoutSeconds: 300,
    values: { existingSecret: credentials.name },
  });

Credentials must be referenced by Secret name/key rather than embedded in Helm values. Readiness errors expose object identity, status codes, replica counts, and condition type/status only; manifest bodies and condition text are not included.

Cloudflare ExternalDNS

Create or explicitly adopt the Cloudflare zone separately, then pass that resource to the add-on:

import { adopt } from "alchemy/AdoptPolicy";

const zone =
  yield *
  Cloudflare.Zone.Zone("PublicZone", {
    name: "example.com",
  }).pipe(adopt(true));

const publicDns =
  yield *
  KubernetesAddons.CloudflareExternalDns("PublicDns", {
    cluster,
    zone,
    policy: "sync",
    proxied: true,
  });

Omit adopt(true) when Alchemy should create a new zone. Cloudflare zones are retained by default, and this add-on never creates or destroys the zone implicitly. Registrar nameserver delegation also remains external.

Unless token is supplied, Alchemy mints an account-owned runtime token with only Zone Read, DNS Read, and DNS Write, scoped to exactly com.cloudflare.api.account.zone.<zoneId>. The deployment credential therefore needs Account API Tokens Write; it additionally needs Zone Read to adopt a zone or Zone Write to create one. Configure it using alchemy login or CLOUDFLARE_ACCOUNT_ID plus CLOUDFLARE_API_TOKEN, never as source code.

The runtime token is written through KubernetesAddons.Secret. Helm receives only the Secret name/key, while the Secret resource version rolls and re-waits the controller after rotation. A pre-created Redacted token can be passed with an optional non-secret tokenRevision when its rotation cannot otherwise be observed safely.

ExternalDNS watches only Services and Ingresses, filters both the zone ID and domain, and records ownership with a stack/stage/resource-specific TXT owner ID. It exclusively owns the A/AAAA/CNAME records it derives and their registry TXT records. Do not declare the same records with Cloudflare.DNS.Record. cert-manager separately owns _acme-challenge TXT records.

With policy: "sync", remove an application's Service/Ingress DNS declaration and wait for reconciliation before destroying ExternalDNS; then its owned records are removed. Destroying the controller itself deliberately performs no zone-wide sweep, so records left behind require explicit cleanup. Use upsert-only when record deletion is not authorized.

cert-manager and Cloudflare ACME

Install cert-manager independently of DNS and application workloads, then add an issuer for one explicit Cloudflare zone:

const certManager =
  yield *
  KubernetesAddons.CertManager("Certificates", {
    cluster,
  });

const issuer =
  yield *
  KubernetesAddons.CloudflareAcmeIssuer("LetsEncrypt", {
    cluster,
    certManager,
    zone,
    email: "[email protected]",
    environment: "staging",
  });

CertManager installs the pinned upstream OCI chart, owns its CRDs, hardens the controller, webhook, and CA injector containers, and returns only after all three workloads and the CRDs are ready. CloudflareAcmeIssuer then creates a zone-scoped account token with only Zone Read and DNS Write, writes it through the write-only Secret resource, and waits for its ClusterIssuer to become ready. The returned issuerRef.name carries that readiness dependency, so a directly composed Certificate is not submitted against an unready issuer.

ExternalDNS and ACME receive separate tokens by default. That lets either credential be rotated or revoked independently: ExternalDNS needs DNS Read for its registry, while cert-manager does not. Supplying the same pre-created Redacted token to both add-ons intentionally couples their permissions, rotation, and outage domain.

Applications own their domains, Certificate manifests, TLS Secret names, and Ingress/Gateway references:

yield *
  Kubernetes.Manifest("ApiCertificate", {
    cluster,
    manifest: {
      apiVersion: "cert-manager.io/v1",
      kind: "Certificate",
      metadata: { name: "api-tls", namespace: "api" },
      spec: {
        secretName: "api-tls",
        dnsNames: ["api.example.com"],
        issuerRef: issuer.issuerRef,
      },
    },
  });

cert-manager generates and rotates the private key inside Kubernetes; Alchemy state contains the public Certificate request but not the issued TLS Secret. cert-manager exclusively owns temporary _acme-challenge records and removes them after validation. It also owns the stable ACME account-key Secret named by the issuer. Do not model either kind of record with Cloudflare.DNS.Record.

Always prove a new setup with environment: "staging". The production smoke check is deliberately manual to avoid consuming Let's Encrypt production rate limits:

  1. Create a distinct production issuer logical resource with environment: "production".
  2. Request one Certificate for a unique hostname and wait for Certificate Ready=True.
  3. Inspect the public certificate issuer and confirm it is not a staging chain; never print the TLS private key.
  4. Remove that Certificate and issuer from the stack, then confirm the exact _acme-challenge record and managed token are gone while the Zone remains.

Parseable

Parseable composes a Namespace, write-only Secret, pinned upstream Helm chart, readiness gate, and optional Ingress. Permanent telemetry lives in the supplied S3BucketAccess; the local PVC is only the durable staging queue:

const parseable =
  yield *
  KubernetesAddons.Parseable("Observability", {
    cluster,
    storage: observabilityBucket,
    staging: { size: "5Gi", storageClass: "hcloud-volumes" },
    ingress: {
      host: "observe.example.com",
      className: "traefik",
      tlsSecretName: "observe-tls",
    },
  });

Omit ingress for the safer ClusterIP-only default. The add-on does not install an ingress controller, manage DNS, or issue the referenced TLS Secret. The bundled OSS UI and the ingestion/query APIs use the same service, so an Ingress exposes all of them.

The flat otel*Endpoint outputs match standard OTEL environment-variable names. endpoints implements the endpoint portion of Alchemy.Telemetry.OtlpOptions and includes the non-secret X-P-Stream and X-P-Log-Source headers. Streams default to otel-logs, otel-traces, and otel-metrics and can be renamed with streams. credentialsSecretRef lets an in-cluster collector mount the Parseable Basic credentials without putting them in Helm values. Temporary S3 session credentials are rejected because the pinned Parseable chart does not support them.

S3-backed container registry

ContainerRegistry deploys a private Docker-compatible OCI registry using the headless zot-minimal image. S3 is authoritative; the pod has no persistent volume, and destroying the add-on never deletes the separately owned bucket or its objects:

const registry =
  yield *
  KubernetesAddons.ContainerRegistry("Images", {
    cluster,
    storage: registryBucket,
    ingress: {
      host: "registry.example.com",
      className: "traefik",
      tlsSecretName: "registry-tls",
    },
    pullSecrets: {
      namespaces: ["api", "workers"],
      name: "private-registry",
    },
  });

The HTTPS Ingress is required because Docker basic credentials must not travel over plaintext. The referenced TLS Secret must be in the registry namespace; the add-on does not install an ingress controller, manage DNS, or issue the certificate. Its Service remains ClusterIP, so the Ingress is the only public exposure. Blob redirects are disabled: clients talk only to the registry and do not need direct access to the S3 endpoint.

The returned Redacted registry.credentials.password and username are used for external docker login and push. The add-on also writes kubernetes.io/dockerconfigjson Secrets into the explicitly listed, pre-existing application namespaces and returns their references. Kubernetes pull Secrets are namespace-local, which is why namespaces must be named rather than receiving one cluster-wide credential.

Anonymous access is denied. The single generated account has read, create, update, and delete access. Its password and an independent bcrypt salt are stable Alchemy Random resources; Zot receives only a cost-12 bcrypt entry. Storage keys, optional session tokens, bcrypt data, and Docker configs go through write-only Secrets and never Helm values. Rotating either credential updates the corresponding Secret and rolls the one-replica deployment.

Garbage collection runs inside Zot every 24 hours by default, starts only in the 02:00-04:00 UTC window, and waits 24 hours before reclaiming untagged, unreferenced content. garbageCollection can override those Zot/Go durations and the UTC window. storagePrefix defaults to registry, allowing the consumer to reserve a collision-free key prefix without putting prefix policy in S3BucketAccess.

By default the add-on owns a new registry namespace. Set createNamespace: false when a Certificate or another stack already owns that namespace. Destroying an owned registry namespace also removes its pull Secret only when that pull Secret lives there; pull Secrets in application namespaces are deleted individually without deleting those namespaces.

OpenTelemetry collector gateway

OtelCollector is a destination adapter for any Kubernetes.ClusterLike. It accepts the endpoint shape from Alchemy.Telemetry.OtlpOptions, adds optional Basic authentication from a namespaced Secret, and exposes Axiom-shaped in-cluster OTLP/HTTP endpoints:

const collector =
  yield *
  KubernetesAddons.OtelCollector("TelemetryGateway", {
    cluster,
    destination: {
      endpoints: parseable.endpoints,
      authentication: {
        type: "basic",
        secretRef: parseable.credentialsSecretRef,
      },
    },
  });

When authentication is configured, the collector runs in the Secret's namespace because Kubernetes cannot reference Secrets across namespaces. It adopts that namespace instead of claiming ownership. Destination headers are stored in a collector-owned Secret; the Helm values and generated ConfigMap contain only environment-variable placeholders. Secret resource versions are copied to pod annotations so a credential update rolls the Deployment.

Applications send unauthenticated OTLP/HTTP to the ClusterIP-only gateway:

vars: {
  OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: collector.otelTracesEndpoint,
  OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: collector.otelLogsEndpoint,
}

The first release deliberately enables only port 4318 and only destination pipelines that were configured. It does not expose Ingress, OTLP/gRPC, host log collection, Kubernetes event collection, or cluster-wide scraping; those need different trust and RBAC boundaries.