logo

Syntax Reference

Configuration is defined using the CUE language.

Every directory acts as a package. Each .cue file may contain one or multiple top-level definitions:

Advanced definitions:

Packages live under a module, which is defined by ns-workspace.cue.

Packages

The package is the unit in Namespace's package management. Packages can declare a set of different type of primitives (see below), and can depend on other packages. Packages can live in different repositories (declared as modules). ns automatically downloads dependencies into a local cache as needed.

The following are the set of primitives that can be defined within a package.

server

Defines a server in this package. Only a single server is supported per package, but the actual source code may be elsewhere.

  • name (string): the stable ID for the server. It affects the names of the corresponding Kubernetes resources, generated URLs, etc.

  • integration/image/imageFrom: See Image building and integrations.

  • args: a list or a map of command-line arguments to pass to the server. Examples:

    args: ["myarg"]
     
    // Gets translated to "--myarg=myvalue"
    args: {
      myarg: "myvalue"
    }
  • env: a map of environment variables to pass to the server. Environment variables may also be injected from a secret or a service endpoint defined in the stack. Examples:

    env: {
      APP_USER: "my-name"
      APP_PASSWORD: fromSecret: ":password"
      BACKEND_URL: fromServiceEndpoint: "example.com/path/to/backend:webapi"
    }
  • class: one of stateless (default) or stateful. "stateless" corresponds to the Kubernetes Deployment resource, while "stateful" corresponds to StatefulSet.

  • requires: a list of package names of servers that need to be deployed together with this server. Their addresses are available via the runtime config.

  • mounts: a map of mounts to attach to the server. The key is the path inside the container, the value is the volume reference or an inline volume definition. Example:

    mounts: {
      "/data": ":my-volume" // my-volume lives in the same package
      "/data2": "example.com/path/to/package:my-other-volume"
      "/data3": kind: "namespace.so/volume/ephemeral"
    }
  • services: a map of services to expose. See the services section for more details. Example:

    services: {
      webapi: {
        port: 4000
        kind: "http"
        ingress: true
      }
    }
  • resources: a list of resource references that need to be deployed with this server. Their instances are available via the resource config. Alternatively, resources may be a map of inline resource definitions. Example:

    resources: {
    	myDatabase: {
    		kind: "example.com/storage:Database"
    		on:   "example.com/storage/postgres"
     
    		input: {
    			name: "mydatabase"
    		}
    	}
    }

Services

A server may expose one or multiple services. A service is an exposed port with a name and an additional metadata, that helps Namespace to correctly forward it. Besides the server itself, sidecars may also expose services.

Fields:

  • port (int): the port number to expose.

  • kind (string): the kind of the service. Currently only http is supported.

  • ingress: the ingress configuration. If true or an object, the service will be exposed via an ingress controller using a Namespace-managed domain. Depending on the deployment cluster, this domain is local or public.

    In the full object form, the following fields are supported:

    • httpRoutes: a map of HTTP routes to expose. The key is the domain wildcard pattern (matching not implemented yet), the value is a list of path prefixes. The user's server needs to handle requests for the declared paths. Defaults to "*": ["/"]. Example:

      httpRoutes: {
        "*.example.com": ["/api"]
      }

sidecars

Additional binaries to run alongside the main server binary. They are commonly used to separate helper functionality from the main application logic (e.g. log collection). The package must have a server. A sidecar may serve a service port.

Syntax: a map, with the stable sidecar name as the key and the sidecar definition as the value:

volumes

A map from volume names to their definitions.

Volume definition: full form:

"volume-name": {
	kind:       "<volume kind url>"
	<volume-specific arguments>
}

Short form:

"volume-name": <volume kind keyword>: {
  <volume-specific arguments>
}

The following volume kinds are supported (short/full forms):

  • ephemeral / namespace.so/volume/ephemeral: an ephemeral volume that vanishes between deployments.
  • persistent / namespace.so/volume/persistent: a fixed-size volume that persists between deployments.
    • id: the stable ID of the volume, propagated to Kubernetes.
    • size: the size of the volume, e.g. 1GiB.
  • configurable / namespace.so/volume/configurable: providing static or generated content as a volume. If the value is a string, it is interpreted as the file content. If the value is a map, the following fields are supported:
    • fromDir: the directory to copy the files from, relative to the package.
    • fromFile: the file to copy the content from, relative to the package.
    • fromSecret: reference to the secret, either just a :<secret name> (for secrets in the same package) or in the form of <full package name>:<secret name>. The secret value is provided as the file content at runtime.
    • fromKubernetesSecret: mounts a Kubernetes secret. Format: <secret name>:<key name>.

secrets

A secret is a named entity whose value is provided at runtime. Secret values can be encrypted, user-provided values that are commited to the repository using ns secrets command. Committed secrets may be set per server that reads them, or pinned for an entire workspace. For simplicity, Namespace also supports generated secrets. These will be generated dynamically during provisioning.

Syntax: a map, with the secret name as the key and the secret definition as the value:

  • description: a description of the secret for debugging.
  • generate: configuration block for generated secrets
    • uniqueId: unique identifier for Kubernetes deployment
    • randomByteCount: amount of bytes in generated secret
    • format: Valid values: FORMAT_BASE64 (default), FORMAT_BASE32

tests

A test is a binary that can be executed together with the given server stack. A Namespace runtime config is available the test binary to determine the addresses of the servers under test.

Syntax: a map, with the test name as the key and the test definition as the value:

  • integration/image/imageFrom: See Image building and integrations.
  • serversUnderTest: an optional list of package names of the servers to start up. If a test is defined in a package with a server, that server is added to this list automatically.

Modules

Modules group multiple packages together into a single versioning boundary. Every package that is under a module, has the same version.

Module name

A module name must correspond to a valid repository path, as ns uses the module name to fetch and manage modules as needed.

module_name: "github.com/username/repo"

Dependencies

Modules can depend on other modules, which allows applications to use servers, resources, etc from packages that live in a dependency.

Dependencies are managed via ns mod, but can also be edited manually in the module definition (ns-workspace.cue).

dependency: {
	"namespacelabs.dev/foundation": {
		version: "f8ad005f1e92ceb88118d2296840703b0ddcbbe8"
	}
}

When performing changes over multiple repositories, it's often useful to be able to use the latest changes made to a dependent repository, without pushing those changes to HEAD. That can be achieved using replace.

replace: {
	"namespacelabs.dev/foundation": "../path/to/local/foundation"
}

Resource management

Resources are Namespace's solution for managing dependencies. They encapsulate the instance of infrastructure, with its supporting code, and automation. Users declare resource instances, which lead to Namespace to orchestrate creating those resources as needed (e.g. because a server depends on them).

The primitives below are all declared at the package level.

resources

Resources can be used to model databases, caches, etc. They are deployable units that have a well-defined lifecycle. Resources have an identity, must be created explicitly, can be owned by other resources or servers, and can be destroyed.

Syntax: a map, with the resource name as the key and the resource definition as the value:

  • class: a reference to the resource class that is being instantiated.
  • provider: a reference to the provider that will provide the instance.
  • resources: a map of resources this resource depends on. Key is the name under which the dependecy will be made availabe. Value is the reference of the resource dependecy.
  • intent: sets the intent expressed by the resource class directly in CUE.

resourceClasses

A resource class defines intent and instance type for a resource. There can be multiple providers for a resource class. A good analogy is that resource classes define interfaces and providers implement that interface. Currently, we only support Protocol Buffers as intent and instance types.

Syntax: a map, with the resource class name as the key and the resource class definition as the value:

  • intent: intent type definition
    • type: fully qualified protobuf type
    • source: path to the .proto file that contains the protobuf definition. May be a relative path.
  • produces: instance type definition
    • same schema as intent

providers

Providers follow a resource class definition and provide and instance from an intent. Providers can depend on other resources, fill statrun initializers on resource creation.

  • initializedWith: Allows specifying a binary invocation that will produce the intent. The intent is provided to the binary as a marshaled string flag named --intent.
  • resources: a list of resource references that need to be deployed with this server. Their instances are available to the initializer via the resource config. The resource config is provided to the binary as a marshaled string flag named --resources. Alternatively, resources may be a map of inline resource definitions.
  • inputs: a map of resources this provider depends on. Key is the name of the resource. Value is the reference of the resource class.

Runtime configurations

At runtime, Namespace injects two generated configurations which servers may use.

  • /namespace/config/runtime.json includes the list of services exported, including ports, and the server's dependencies, and their endpoints. See config.proto for more details.

  • /namespace/config/resources.json includes the instances of any required resource. Instances are keyed by their resource package reference. The value is the populated resource instance definition. Example:

    {
    	"example.com/server:myDatabase": {
    		"url": "http://foo.nscloud.dev/db"
    	},
    	"example.com/server:myCache": {
    		"capacity": "1GiB"
    	}
    }

    This allows easy access of instance values for dynamically typed languages (e.g. TypeScript). For Go, we provide a helper library.

Image building and integrations

Servers, sidecars and tests can be built from source code (using imageFrom field) or from a pre-built image from an image registry (using image field).

For servers and tests it is recommended to use integration instead of imageFrom (with a similar syntax). An integration provides a deep support for a particular language/framework. In addition to image building, an integration may do other things, such as setting up file sync for Node.js development.

image

A pre-built image from an image registry to use for the server or test. Example:

image: "postgres:[email protected]:db927beee892dd02fbe963559f29a7867708747934812a80f83bff406a0d54fd"

imageFrom/integration

imageFrom and integration fields in general have the same syntax, with some fields only available for a specific case.

Full form:

integration|imageFrom: {
	kind:       "<integration url>"
	<integration-specific arguments>
}

Short forms:

integration|imageFrom: <integration short keyword>: {
	<integration-specific arguments>
}
 
// When there are no arguments
integration|imageFrom: "<integration short keyword>"

The following integrations are supported (short/full forms):

  • dockerfile / namespace.so/from-dockerfile: an opaque Dockerfile. Arguments:

    • src: the path to the Dockerfile. Can also be specified as dockerfile: "<file path>". Defaults to "Dockerfile".
  • go / namespace.so/from-go: a Go binary. Arguments:

    • pkg: the Go package to build. Defaults to ".".
  • nodejs / namespace.so/from-nodejs: a Node.js application. Arguments:

    • pkg: the Node.js package to build. Defaults to ".".
    • backends: see the Web backends below.
  • web / namespace.so/from-web: a pure browserside Node.js application (e.g. implemented with Vite or Create-React-App). Supported only as integration. Arguments:

    • pkg: the Node.js package to build. Defaults to ".".
    • service: a name of the service that corresonds to the Web server, required. This is used for non-dev builds when prebuilt, static files are served (opposed to the hot-reload server, e.g. vite, for development).
    • build: build-related arguments:
      • outDir: the directory to output the built files to when building for non-dev environments. Defaults to dist.
    • backends: a map from an alias to the backend package address. This makes Namespace generate a src/config/backends.ns.js file in the built image that contains the backend URLs, accessible from the browser. The runtime config wouldn't work here as its content is not available from the browser.
  • shellscript/namespace.so/from-shellscript: wraps a shell script into an Alpine-based image with bash and curl. Arguments:

    • entrypoint: a path to the script.
    • requiredPackages: additional Alpine packages to install. For example, ["jq"].