Apex lets you build, deploy, and manage AWS Lambda functions with ease. With Apex you can use
languages that are not natively supported by AWS Lambda, such as Golang, through the use
of a Node.js shim injected into the build. A variety of workflow related tooling is provided for
testing functions, rolling back deploys, viewing metrics, tailing
logs, hooking into the build system and more.

AWS credentials

Before using Apex you need to first give it your account credentials so that Apex can manage resources. There are a number of ways to do that, which are outlined here.

Via environment variables

Using environment variables only, you must specify the following:

AWS_ACCESS_KEY_ID AWS account access key

AWS_SECRET_ACCESS_KEY AWS account secret key

AWS_REGION AWS region

If you have multiple AWS projects you may want to consider using a tool such as direnv to localize and automatically set the variables when
you’re working on a project.

Via ~/.aws files

Using the ~/.aws/credentials and ~/.aws/config files you may specify AWS_PROFILE to tell apex which one to reference. However, if you do not have a ~/.aws/config file, or “region” is not defined, you should set it with the AWS_REGION environment variable. To read more on configuring these files view Configuring the AWS CLI.

Via profile flag

If you have both ~/.aws/credentials and ~/.aws/config you may specify the profile directly with apex --profile <name> when issuing commands. This means you do not have to specify any environment variables, however you must provide it with each operation:

$ apex --profile myapp-prod deploy

Via project configuration

You may store the profile name in the project.json file itself as shown in the following snippet. This is ideal since it ensures that you do not accidentally have a different environment set.

{
"profile": "myapp-prod"
}

Via IAM Role

Using an IAM role can be achieved in two ways, via the AWS_ROLE environment variable or via a command line flag --iamrole. As with other Apex credential loading, the command line flag will supersede the environment variable.

Structuring projects

A “project” is the largest unit of abstraction in Apex. A project consists of collection of AWS Lambda functions, and
all apex(1) operations have access to these functions.

Configuration

Projects have a project.json file in the root directory. This file contains details about your project, as well as
defaults for functions, if desired. Here’s an example of a project.json file declaring a default AWS IAM “role” and “memory” for all functions.

Multiple Environments

Multiple environments are supported with the --env flag. By default project.json and function.json are used, however when --env is specified project.ENV.json and function.ENV.json will be used, falling back on function.json for cases when staging and production config is the same. For example your directory structure may look something like the following:

Symlinks

It’s important to note that Apex supports symlinked files and directories. Apex will read the links and pull in these files, even if the links aren’t to files within your function. This enables the use of npm link, shared configuration and so on.

Fields

name

Name of the project. This field is used in the default value for “nameTemplate” to prevent collisions between multiple projects.

type: string

required

description

Description of the project. This field is informational.

type: string

runtime

Default runtime of function(s) unless specified in their function.json configuration.

type: string

Runtimes supported:

java (Java 8)

python2.7 (Python 2.7)

python3.6 (Python 3.6)

nodejs4.3 (Node.js 4.3)

nodejs4.3-edge (Node.js 4.3 Edge)

nodejs6.10 (Node.js 6.10)

golang (any version)

clojure (any version)

rust-musl[^rust-runtime][^rust-linux-only] (any version)

rust-gnu[^rust-runtime][^rust-linux-only] (any version)

[^rust-runtime]: Rust has two types of libc dependencies and the rust-musl is recommended. Your rust lambda function may refuse to run because of glibc version mismatch between lambda server and your pc when rust-gnu runtime is used.

[^rust-linux-only]: Rust version of lambda currently can only be built on linux machine. If you try to build on MacOS, you will encounter the linker error. One solution is using apex inside a linux docker container on MacOS.

defaultEnvironment

environment

nameTemplate

Template used to compute the function names. By default the template {{.Project.Name}}_{{.Function.Name}} is used, for example project “api” and ./functions/users becomes “api_users”. To disable prefixing, use {{.Function.Name}}, which would result in “users”.

type: string

retainedVersions

Default number of retained function’s versions on AWS Lambda unless specified in their function.json configuration.

vpc

vpc.securityGroups

List of security groups IDs

type: array

vpc.subnets

List of subnets IDs

type: array

Structuring functions

A function is the smallest unit in Apex. A function represents an AWS Lambda function.

Functions must include at least one source file (runtime dependent), such as index.js or main.go. Optionally a function.json file may be placed in the function’s directory, specifying details such as the memory allocated or the AWS IAM role. If one or more functions is missing a function.json file you must provide defaults for the required fields in project.json (see “Projects” for an example).

Configuration

Fields

Fields marked as inherited may be provided in the project.json file instead.

description

Description of the function. This is used as the description in AWS Lambda.

type: string

required

runtime

Runtime of the function. This is used as the runtime in AWS Lambda, or when required, is used to determine that the Node.js shim should be used. For example when this field is “golang”, the canonical runtime used is “nodejs” and a shim is injected into the zip file.

type: string

required

inherited

handler

Event handler name, this is the function invoked for a given event. Defaults are:

deadletter_arn

Deploying functions

To deploy one or more functions all you need to do is run apex deploy. Apex deploys are idempotent; a build is created
for each function, and Apex performs a checksum to see if the deployed function matches the local build, if so
it’s not deployed.

After deploy Apex will cleanup old function’s versions stored on AWS Lambda leaving only few. Number of retained versions
can be specified in project or function configuration.

If you prefer to be explicit you can pass one or more function names to apex deploy. You may also perform shell-style globbing matches with any command accepting function names, such as deploy, logs, and rollback.

Deploy with an alias. The alias is added regardless of changes to the function and its config.

$ apex deploy --alias prod api

Invoking functions

Apex allows you to invoke functions from the command-line, optionally passing a JSON event or stream to STDIN. It’s important to note that invoke will execute the remote Lambda function and not locally execute your function. It will execute the $LATEST Lambda function available.

Deleting functions

Apex allows you to delete functions, however it will prompt by default. Use the -f, --force flag to override this behaviour. You may pass zero or more function names.

Examples

Delete all with prompt:

$ apex delete
The following will be deleted:
- bar
- foo
Are you sure? (yes/no):

For delete of all functions:

$ apex delete -f

For delete of specific functions:

$ apex delete -f foo bar

Delete all functions which name starts with “auth”:

$ apex delete auth*

Building functions

Apex generates a zip file for you upon deploy, however sometimes it can be useful to see exactly what’s included in this file for debugging purposes. The apex build command outputs the zip to STDOUT for this purpose.

Examples

Output zip to out.zip:

$ apex build foo > out.zip

Rolling back versions

Apex allows you to roll back to the previous, or specified version of a function.

Function hooks

Apex supports the notion of hooks, which allow you to execute shell commands throughout a function’s lifecycle. For example you may use these hooks to run tests or linting before a deploy, or to transpile source code using Babel, CoffeeScript, or similar.

Hooks may be specified in project.json or function.json. Hooks are executed in the function’s directory, not the project’s directory. If a hook exits > 0 then Apex will halt and display the error.

Internally Apex uses these hooks to implement out-of-the-box support for Golang and other compiled languages.

Supported hooks

build run before a function zip is built (use this to compile binaries or transform source)

deploy run before a function is deployed (useful for testing, linting)

clean run after a function is deployed (useful for cleaning up build artifacts)

Examples

Viewing log output

Apex is integrated with CloudWatch Logs to view the output of functions. By default the logs for all functions will be displayed, unless one or more function names are passed to apex logs. You may also specify the duration of time in which the history is displayed (defaults to 5 minutes), as well as following and filtering results.

Examples

View all function logs within the last 5 minutes:

$ apex logs

View logs for “uppercase” and “lowercase” functions:

$ apex logs uppercase lowercase

Follow or tail the log output for all functions:

$ apex logs -f

Follow a specific function:

$ apex logs -f foo

Follow filtered by pattern “error”:

$ apex logs -f foo --filter error
$ apex logs -f foo -F error

Output the last hour of logs:

$ apex logs --since 1h
$ apex logs -s 1h

Log all functions which name starts with “auth”:

$ apex logs auth*

Viewing metrics

The apex metrics command provides a quick glance at the overall metrics for your functions, displaying the number of invocations, total execution duration, throttling, and errors within a given time period.

Managing infrastructure

Apex is integrated with Terraform to provide infrastructure management. Apex currently only manages Lambda functions, so you’ll likely want to use Terraform or CloudFormation to manage additional resources such as Lambda sources.

Managing infrastructure

The apex infra command is effectively a wrapper around the terraform command. Apex provides several variables and helps provide structure for multiple Terraform environments.

Each environment such as “prod” or “stage” lives in the ./infrastructure directory. For reference it may look something like:

infrastructure/
├── prod
│ └── main.tf
├── stage
│ └── main.tf

For example apex infra --env prod plan is effectively equivalent to the following command, with many -var’s passed to expose information from Apex.

$ cd infrastructure/prod && terraform plan

The environment is specified via the --env flag, or by default falls back on the defaultEnvironment property of project.json.

Terraform variables

Currently the following variables are exposed to Terraform:

aws_region the AWS region name such as “us-west-2”

apex_environment the environment name such as “prod” or “stage”

apex_function_role the Lambda role ARN

apex_function_arns A map of all lambda functions

apex_function_names A map of all the names of the lambda functions

Notes

You’ll typically need to assign ${apex_function_myfunction}:current to specify that the “current” alias is referenced.

The apex_function_NAME variables are not available until the functions have been deployed (via apex deploy) at least once.

Previewing with dry-run

Apex lets you perform a “dry run” of any operation with the --dry-run flag, and no destructive AWS changes will be made.

Notation

Dry runs use the following symbols:

+ resource will be created

- resource will be removed

~ resource will be updated

Examples

For example if you have the functions “foo” and “bar” which have never been deployed, you’ll see the following output. This output represents the final requests made to AWS; notice how the function names are prefixed with the project’s (“testing”) to prevent collisions, and aliases are made to maintain the “current” release alias.

Environment variables

Apex now supports the new native AWS Lambda environment variables, which are encrypted via KMS. There are several ways to set these variables, let’s take a look!

Flag –set

The -s, --set flag allows you to set environment variables which are exposed to the function at runtime. For example in Node.js using process.env.NAME or in Go using os.Getenv("NAME"). You may use this flag multiple times.

For example suppose you had a Loggly log collector and it needs an API token, you might deploy with:

$ apex deploy -s LOGGLY_TOKEN=token log-collector

Flag –env-file

The -E, --env-file flag allows you to set multiple environment variables using a JSON file.

$ apex deploy --env-file /path/to/env.json

Sample env.json:

{
"LOGGLY_TOKEN": "12314212213123"
}

Config (project.json or function.json)

Specify environment variables in project.json or function.json, note that the values must be strings.

{
"environment": {
"LOGGLY_TOKEN": "12314212213123"
}
}

Precedence

The precedence is currently as follows:

-s, --set flag values

-E, --env-file file values

environment variables specified in project.json or function.json

Omitting files

Optional .apexignore files may be placed in the project’s root, or within specific function directories. It uses .gitignore pattern format; all patterns defined, are relative to function directories and not the project itself. By default both .apexignore and function.json are ignored.

Example

Understanding the shim

Apex uses a Node.js shim for non-native language support. This is a very small program which executes in a child process, and feeds Lambda input through STDIN, and program output through STDOUT. Because of this STDERR must be used for logging, not STDOUT.

Viewing documentation

The apex docs command lets you read this documentation from the command line. By default it is piped into the less(1) pager so that you can perform operations like scrolling or searching from the terminal.

Tips

Type / and search a keyword, and press enter to search the document

Type n to view the next occurrence of your query

Type u to page up

Type d to page down

Type q to exit

Upgrading Apex

The apex upgrade command will update your installation of apex(1) :).

FAQ

How do you manage multiple environments?

It’s highly recommended to create separate AWS accounts for staging and production environments. This provides complete isolation of resources, and allows you to easily provide granular access to environments as required. See AWS credentials for supplying an account profile.

AWS IAM roles can be used to provide quick access to each environment using a drop-down in the AWS Console.

Can I test functions locally?

Currently there is no way to run functions locally, it would be a very large task to emulate the AWS. We recommend writing the bulk of your logic as libraries or packages native to your chosen language, using only thin connective layers in the Lambda functions themselves. This makes it easy to unit-test your functions, and makes them more portable if you’re worried about vendor lock-in.

How is this different than Serverless?

Serverless uses CloudFormation to bootstrap resources, which can be great for getting started, but is generally less robust than Terraform for managing infrastructure throughout its lifetime. For this reason Apex does not currently provide resource management. This may change in the future for light bootstrapping, likely in an optional form.

At the time of writing Serverless does not support shimming for languages which are not supported natively by Lambda, such as Golang. Apex does this for you out of the box.

The structures imposed by each project are different, as well as varying features, see the documentation for each project to see what either supports.

Serverless aims to be provider agnostic, which can be both a pro and a con depending on the level of abstraction you’re comfortable with, and if you desire to have a tool modelled closer to a single provider’s capabilities. This similar to the contention around ORM vs “raw” queries.

Serverless is written using Node.js, Apex is written in Go. Apex aims to be a simple and robust solution, while Serverless intends on providing a more feature-rich solution, pick your poison.

Is using the Node.js shim slow?

The shim creates a child process, thus creates a few-hundred millisecond delay for the first invocation. Subsequent calls to a function are likely to hit an active container, unless the function sees very little traffic, after which the latency is roughly 0.5ms.

Do shimmed languages have any limitations?

The shim currently operates using JSON over stdout, because of this you must use stderr for logging.

Can I manage functions that Apex did not create?

Apex is not designed to handle legacy cases, or functions created by other software. For most operations Apex references the local functions, if it is not defined locally, then you cannot operate against it. This is by-design and is unlikely to change.