Applications
Application Modules provide infrastructure and configuration required to launch and maintain Applications through Nullstone.
These modules enable a suite of features in the Nullstone UI, CLI, and Engine beyond a normal Terraform module. This document provides reference to build your own application module that enables each of these features.
Managed Artifacts
In Nullstone, your application code is deployed as Docker containers, archive files (zip), or directories. Nullstone provides a simple mechanism to deploy new artifacts without the need for special tooling based on the cloud provider or platform (e.g. Lambda, Fargate, Kubernetes).
The Application module is responsible for provisioning the docker registry or storage bucket where the artifacts are stored. Additionally, the application module needs to output the artifact store and authorized user to push an artifact. Refer to the sections below for usage, outputs, and supported applications fore each of the supported packaging formats. (Docker Image, Zip File, Directory)
Docker Image
CLI Usage:
nullstone push --source=<docker-image> --app=my-app --env=dev
Contract Outputs:
image_repo_url
- A Docker repository URL where the images are pushedimage_pusher
- A user with permission to push docker images toimage_repo_url
Supported App Contracts:
Zip File
CLI Usage:
nullstone push --source=<archive>.zip --app=my-app --env=dev
Contract Outputs:
artifacts_bucket_name
- A bucket name where artifacts are stored.artifacts_key_template
- A template string used to name versioned artifacts in the storage bucket.deployer
- A user with explicit access to upload artifacts to the storage bucket.
Supported App Contracts:
Directory
CLI Usage:
nullstone push --source=./<directory> --app=my-app --env=dev
Contract Outputs:
bucket_name
- A bucket name where artifacts are stored.deployer
- A user with explicit access to upload artifacts to the storage bucket.
Supported App Contracts:
Automated Deploys and Rollbacks
In Nullstone, your application code is deployed either as Docker containers, archive files (zip), or directories. Nullstone provides a simple mechanism to deploy new versions of code without the need for special tooling based on the cloud provider or platform (e.g. Lambda, Fargate, Kubernetes). This enables Nullstone to:
- Provide an auditable list of app versions
- Track health of app version deployments
- Enable rolling back to previous versions of the app
Application Version
In Nullstone, you can deploy a new version of your application through the CLI/UI or Terraform. To ensure the deployed application version is synchronized, the Nullstone Terraform provider offers a data source named ns_app_env
. The ns_app_env
data source exposes a version
attribute that refers to the currently-deployed version of the application.
If you use nullstone modules generate
, the following app.tf
file is generated for you.
data "ns_app_env" "this" {
stack_id = data.ns_workspace.this.stack_id
app_id = data.ns_workspace.this.block_id
env_id = data.ns_workspace.this.env_id
}
locals {
app_version = data.ns_app_env.this.version
}
Environment Variables
Nullstone allows developers to configure applications by injecting environment variables. Nullstone injects these into your application module through the service_env_vars
variable. If you are creating an application module, make sure to add it:
variable "env_vars" {
type = map(string)
default = {}
description = <<EOF
The environment variables to inject into the service.
These are typically used to configure a service per environment.
It is dangerous to put sensitive information in this variable because they are not protected and could be unintentionally exposed.
EOF
}
These user-supplied environment variables should be merged with environment variables from other sources. Other sections in this reference use this base to add additional environment variables from capabilities.
locals {
standard_env_vars = tomap({
NULLSTONE_ENV = data.ns_workspace.this.env_name
})
env_vars = [for k, v in merge(local.standard_env_vars, var.env_vars) : { name = k, value = v }]
}
Public/Private URLs
Nullstone provides developers with visibility into which URLs you can access an application. In this screenshot, api-gateway is accessible inside the network via http://api-gateway.service:80
and externally via https://api.dev.nullstone.io:443
.
In the official Nullstone modules, these URLs are aggregated from:
- Public entry capabilities (see Automated public access)
- Automatic service discovery
If you are creating or configuring an application module, make sure to output URLs through public_urls
and private_urls
outputs. If you use nullstone modules generate
, the outputs.tf
file contains code to output URLs from capabilities. It is best to add additional URLs to the locals additional_private_urls
and additional_public_urls
.
SSH Access
Nullstone provides a CLI command ssh
that enables securely connecting to apps over SSH. This functionality provides cheap, valuable connectivity for teams that do not have VPN or Zero-Trust Access configured.
CLI Usage:
nullstone ssh --app=my-app --env=dev
Contract Outputs:
region
- Region in cloud provider where app is deployed to query app information.cluster_arn
- Cluster identifier where app is hosted to query app information.service_name
- Name of app service to query app information.main_container_name
- Specifies the primary container through which the CLI makes an SSH connection.deployer
- A user with explicit access to establish an SSH connection.
Supported App Contracts:
High-level Application Status
Nullstone provides a CLI command status
that enables viewing application status. This functionality provides cheap, valuable diagnostics without logging into the AWS console.
CLI Usage:
nullstone status --watch --app=my-app --env=dev
Contract Outputs:
region
- Region in cloud provider where app is deployed to query application status.deployer
- A user with explicit access to read service and task status of application.
Supported App Contracts:
Application Log Viewing
Nullstone provides a CLI command logs
that enables viewing application logs. This functionality provides cheap, valuable diagnostics using the cloud provider's built-in logging functionality without downloading additional tooling.
CLI Usage:
nullstone logs --tail --start '5m' --app=my-app --env=dev
Contract Outputs:
log_provider
- The type of log provider.log_group_name
- The name of the log group in the log provider that holds logs.log_reader
- A user with explicit access to read logs from the log provider.
Support Log Providers:
cloudwatch
Supported App Contracts:
Containers
Container applications make use of a docker registry for artifacts.
When deploying a new version of the container, the image is tagged with the version name. Then, the app is redeployed with the new image tag.
If you want to create your own container app module, make sure to:
- Configure the app in your Terraform module to use the app version.
- Configure the "main" container name. (The container that is versioned by the app)
- Output the
main_container_name
.
Here is example taken from nullstone-modules/aws-fargate-service:
locals {
main_container_name = "main"
container_definition = {
name = local.main_container_name
image = "${local.service_image}:${local.app_version}"
}
}
output "main_container_name" {
value = local.container_definition.name
description = "string ||| The name of the container definition for the main service container"
}
Static Site
Typically, static sites are deployed to a content delivery network (CDN). The content is served close to users around the globe from an object bucket (e.g. S3 in AWS).
When deploying a new version of a static site in Nullstone, the content is placed into an object bucket with the version name as the directory. Then, the CDN is configured to serve assets out of that version's directory.
Nullstone comes with a CDN module (e.g. AWS - nullstone-modules/aws-s3-cdn) that attaches to a static site. However, if you want to create your own CDN module, make sure the CDN is configured to serve out of the correct directory based on the app version. Here is example taken from the Nullstone AWS S3 CDN capability:
resource "aws_cloudfront_distribution" "this" {
origin {
origin_path = trimsuffix("/${local.app_version}", "/")
// more
}
// more
}
Capabilities
In the Nullstone UI, you can dynamically add functionality to applications using special Nullstone modules called Capabilities. The Nullstone engine dynamically adds these modules to the application module when applying the application infrastructure changes.
TIP
When creating an application module that supports capabilities, use the nullstone modules generate
CLI command. This command creates a capabilities.tf.tmpl
file that the Nullstone engine uses to dynamically create Terraform for attached capability modules.
The application module communicates information about the application (e.g. security group, role, etc.) through Terraform variables. The capability module creates cloud resources that grants network access, creates credentials, and more. Finally, the capability module emits outputs (e.g. configuration, secrets, etc.) that the application module aggregates into the final application definition. See Extending > Capabilities to learn more about capabilities.
Automated env variables
In Environment Variables section above, the application was configured to merge built-in environment variables with user-supplied. Additionally, application modules receive env
output from capabilities as environment variables. Here is an example snippet from nullstone-modules/aws-fargate-service that merges in env vars from capability outputs.
container_definition = {
// ... other configuration
environment = concat(local.env_vars, try(local.capabilities.env, []))
}
Format:
output "env" {
value = [
{
name = "ENV_VAR_NAME"
value = "env-value"
}
]
}
Supported App Contracts:
Automated secrets
Similar to Automated Env Variables, application modules receive secrets
output from capabilities as sensitive configuration. The application module is responsible for securing these secrets in a secrets vault and injecting into the application.
Refer to nullstone-modules/aws-fargate-service/secrets.tf for an example of how to consume secrets from capabilities and add to AWS Secrets Manager. Note that the secret ARN is injected into the service. Here is a small snippet from the same module granting the app access to these secrets.
locals {
// These are used to generate an IAM policy statement to allow the app to read the secrets
secret_arns = [for as in aws_secretsmanager_secret.app_secret : as.arn]
secret_statement_resources = length(local.secret_arns) > 0 ? [local.secret_arns] : []
}
data "aws_iam_policy_document" "execution" {
dynamic "statement" {
for_each = local.secret_statement_resources
content {
sid = "AllowReadSecrets"
effect = "Allow"
resources = statement.value
actions = [
"secretsmanager:GetSecretValue",
"kms:Decrypt"
]
}
}
}
Supported App Contracts:
Automated datastore access
When creating an application module to support datastore capabilities, make sure to configure the following:
- Handle
env
outputs from capability modules. (see Automated env variables) - Handle
secrets
outputs from capability modules. (see Automated secrets) - Inject a network identity for the capability to grant network access.
- Inject an IAM role for the capability to grant any provider-specific privileges.
The following snippet taken from nullstone-modules/aws-fargate-service demonstrates injecting the network identity (security_group_id
) and the IAM role (role_name
) into capabilities. local.app_metadata
is passed into each capability module as a variable in capabilities.tf.tmpl
.
locals {
app_metadata = tomap({
security_group_id = aws_security_group.this.id
role_name = aws_iam_role.task.name
})
}
Automated public access
By default, applications are deployed into private subnets which are inaccessible to consumers outside the network. To expose an application to the public, you attach a capability to the application that creates a public entry device (e.g. Load Balancer, CDN, API Gateway). Usually, you attach a subdomain in the Nullstone UI and Nullstone automatically selects a capability module that matches the application module.
Adding public access to an application module is specific to the application platform and the type of public entry device. Depending on your app platform and public entry target, make sure to configure the following:
- Handle public URLs. (see Public/Private URLs).
- [Optional] Inject network identity or app metadata.
- [Optional] Register app with public entry targets.
Refer to the official Nullstone modules for examples on how to connect.
Third-party Logs/Metrics
Frequently, software teams use third-party logs and metrics providers. By attaching a telemetry capability to your app, you can change the where your logs and metrics go.
When creating application modules to support these log providers, it's important to read your specific log provider instructions. Most log capabilities stream logs from a cloudwatch log group created by the application to the third party. To support these capabilities, make sure to inject the log group name through app_metadata
capability variable. Here is an example app.tf
from nullstone-modules/aws-fargate-service.
locals {
app_metadata = tomap({
// Inject app metadata into capabilities here (e.g. security_group_name, role_name)
security_group_id = aws_security_group.this.id
role_name = aws_iam_role.task.name
main_container = local.main_container_name
service_port = var.port
log_group_name = module.logs.name
internal_subdomain = var.port == 0 ? "" : "${local.block_name}.${local.service_domain}"
})
}
Sidecars
A sidecar is another container that runs alongside your primary application container. Sidecars are used to add application monitoring, service mesh attachment, request proxying, and more. This is possible because sidecars operate on the same network interface as the primary application container; the sidecar can communicate with the app container over local networking.
Here are two examples of sidecars:
- Nginx Sidecar - Serves web app through nginx to optimize static asset delivery.
- Envoy Proxy - Adds an envoy proxy in front of the app container.
Adding support for sidecars in your application module relies heavily on your platform (e.g. Kubernetes, Fargate, etc.). To build support in your application module, make sure to:
- Receive
sidecars
output from capabilities and merge into container/pod definitions. - Configure each attached sidecar with your log configuration.
- Redirect any public entry appliances to any proxy sidecars. (Usually done by reading
sidecars[].owns_service_port = true
from capabilities)