Up Deploy serverless apps in seconds

Introduction

Up deploys infinitely scalable serverless apps, APIs, and static websites in seconds, so you can get back to working on what makes your product unique.

Up focuses on deploying “vanilla” HTTP servers so there’s nothing new to learn, just develop with your favorite existing frameworks such as Express, Koa, Django, Golang net/http or others.

Up currently supports Node.js, Golang, Python, Crystal, and static sites out of the box. Up is platform-agnostic, supporting AWS Lambda and API Gateway as the first targets. You can think of Up as self-hosted Heroku style user experience for a fraction of the price, with the security, flexibility, and scalability of AWS.

Installation

Up is distributed in a binary form and can be installed manually via the tarball releases or one of the options below.

The quickest way to get up is to run the following command, which installs to to /usr/local/bin by default.

$ curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh

NPM’s up package runs the same script as above.

$ npm i -g up

Verify installation with:

$ up version

Later when you want to update up to the latest version use the following command:

$ up upgrade

If you hit permission issues, you may need to run the following, as up is installed to /usr/local/bin/up by default.

$ sudo chown -R $(whoami) /usr/local/bin/

AWS Credentials

Before using Up you need to first provide your AWS account credentials so that resources can be created. There are a number of ways to do that, which are outlined here.

Via environment variables

Using environment variables only, you may 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 file to store credentials, allowing you to specify AWS_PROFILE so Up knows which project to reference. To read more on configuring these files view Configuring the AWS CLI.

Here’s an example of ~/.aws/credentials, where export AWS_PROFILE=myapp would activate these settings.

[myapp]
aws_access_key_id = xxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx

Via project configuration

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

{
  "profile": "myapp"
}

IAM Policy for Up CLI

Below is a policy for AWS Identity and Access Management which provides Up access to manage your resources.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "route53:*",
        "route53domains:*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "acm:*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "cloudfront:*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "cloudformation:Create*",
        "cloudformation:Update*",
        "cloudformation:Delete*",
        "cloudformation:Describe*",
        "cloudformation:ExecuteChangeSet"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "iam:AttachRolePolicy",
        "iam:CreatePolicy",
        "iam:CreateRole",
        "iam:DeleteRole",
        "iam:DeleteRolePolicy",
        "iam:GetRole",
        "iam:PassRole",
        "iam:PutRolePolicy"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "lambda:Create*",
        "lambda:Delete*",
        "lambda:Get*",
        "lambda:List*",
        "lambda:Update*",
        "lambda:AddPermission",
        "lambda:RemovePermission",
        "lambda:InvokeFunction"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": [
        "logs:Create*",
        "logs:Put*",
        "logs:Test*",
        "logs:Describe*",
        "logs:FilterLogEvents"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudwatch:Get*"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "apigateway:*"
      ],
      "Resource": [
        "arn:aws:apigateway:*::/*"
      ]
    }
  ]
}

Note that this may change as features are added to Up, so you may have to adjust the policy.

Getting Started

The simplest Up application is a single file for the application itself, with zero dependencies.

Up runs “vanilla” HTTP servers listening on the PORT environment variable, which is passed to your program by Up. For example create a new directory with the following app.js file:

const http = require('http')
const { PORT = 3000 } = process.env

http.createServer((req, res) => {
  res.end('Hello World from Node.js\n')
}).listen(PORT)

Next you’ll need to let Up know which profile in ~/.aws/credentials to use. Note however that instead of assigning AWS_PROFILE you may use the "profile": "NAME" option in up.json.

$ export AWS_PROFILE=myapp

Deploy it to the development stage:

$ up

Open up the URL in your browser:

$ up url --open

Or copy it to the clipboard:

$ up url --copy

Or test with curl:

$ curl `up url`

That’s it! You’ve deployed a basic Up application. Note that the first deploy may take a minute to set up the resources required. To delete it and its resources, use the following command:

$ up stack delete

If you’re not a Node.js developer here are some examples in additional languages.

For Python create app.py:

from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import os

class myHandler(BaseHTTPRequestHandler):
  def do_GET(self):
  self.send_response(200)
  self.send_header('Content-type','text/html')
  self.end_headers()
  self.wfile.write("Hello World from Python\n")
  return

server = HTTPServer(('', int(os.environ['PORT'])), myHandler)
server.serve_forever()

For Golang create main.go:

package main

import (
  "os"
  "fmt"
  "log"
  "net/http"
)

func main() {
  addr := ":"+os.Getenv("PORT")
  http.HandleFunc("/", hello)
  log.Fatal(http.ListenAndServe(addr, nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Hello World from Go")
}

Finally for Crystal create main.cr:

require "http/server"

port = ENV["PORT"].to_i

server = HTTP::Server.new(port) do |ctx|
  ctx.response.content_type = "text/plain"
  ctx.response.print "Hello world from Crystal"
end

server.listen

Configuration

Configuration for your app lives in the up.json within your project’s directory. This section details each of the options available.

Name

The name of the application, which is used to name resources such as the Lambda function or API Gateway. By default this is the directory name.

{
  "name": "api"
}

Profile

The profile property is equivalent to setting AWS_PROFILE for referencing AWS credentials in the ~/.aws directory. Use of this property is preferred it prevents accidents with environment variables.

{
  "profile": "someapp"
}

Regions

You may specify one or more target regions for deployment using the regions array. Glob style patterns may be used to match region ids. By default “us-west-2” is used unless the AWS_REGION environment variable is defined.

A single region:

{
  "regions": ["us-west-2"]
}

Several regions:

{
  "regions": ["us-west-2", "us-east-1", "ca-central-1"]
}

USA and Canada only:

{
  "regions": ["us-*", "ca-*"]
}

Western USA only:

{
  "regions": ["us-west-*"]
}

All regions like a boss:

{
  "regions": ["*"]
}

Currently Lambda supports the following regions:

  • us-east-2 – US East (Ohio)
  • us-east-1 – US East (N. Virginia)
  • us-west-1 – US West (N. California)
  • us-west-2 – US West (Oregon)
  • ap-northeast-2 – Asia Pacific (Seoul)
  • ap-south-1 – Asia Pacific (Mumbai)
  • ap-southeast-1 – Asia Pacific (Singapore)
  • ap-southeast-2 – Asia Pacific (Sydney)
  • ap-northeast-1 – Asia Pacific (Tokyo)
  • ca-central-1 – Canada (Central)
  • eu-central-1 – EU (Frankfurt)
  • eu-west-1 – EU (Ireland)
  • eu-west-2 – EU (London)
  • sa-east-1 – South America (São Paulo)

WARNING: multi-region support won’t be complete until https://github.com/apex/up/issues/134 is closed.

Lambda Settings

The following Lambda-specific settings are available:

  • role – IAM role ARN, defaulting to the one Up creates for you
  • memory – Function memory in mb (Default 128, Min 128, Max 1536)
  • timeout – Function timeout in seconds (Default 15, Min 1, Max 300)

For example:

{
  "name": "api",
  "lambda": {
    "memory": 512,
    "timeout": 2
  }
}

View the Lambda Pricing page for more information regarding the memory setting.

Hook Scripts

Up provides “hooks” which are commands invoked at certain points within the the deployment workflow for automating builds, linting and so on. The following hooks are available:

  • build – Run on deploy, before building the zip file
  • clean – Run after a build to clean up artifacts

Here’s an example using Browserify to bundle a Node application. Use the -v verbose log flag to see how long each hook takes.

{
  "name": "app",
  "hooks": {
    "build": "browserify --node app.js > server.js",
    "clean": "rm server.js"
  }
}

Up performs runtime inference to discover what kind of application you’re using, and does its best to provide helpful defaults. See the “Runtimes” section.

Multiple commands be provided by using arrays, and are run in separate shells:

{
  "name": "app",
  "hooks": {
    "build": [
      "mkdir -p build",
      "cp -fr static build",
      "browserify --node index.js > build/client.js"
    ],
    "clean": "rm -fr build"
  }
}

Static File Serving

Up ships with a robust static file server, to enable it specify the app type as "static".

{
  "type": "static"
}

By default the current directory (.) is served, however you can change this using the dir setting. The following configuration restricts only serving of files in ./public/*, any attempts to read files from outside of this root directory will fail.

{
  "name": "app",
  "type": "static",
  "static": {
    "dir": "public"
  }
}

Environment Variables

The environment object may be used for plain-text environment variables. Note that these are not encrypted, and are stored in up.json which is typically committed to GIT, so do not store secrets here.

{
  "name": "api",
  "environment": {
    "API_FEATURE_FOO": "1",
    "API_FEATURE_BAR": "0"
  }
}

These become available to you via process.env.API_FEATURES_FOO, os.Getenv("API_FEATURES_FOO") or similar in your language of choice.

The following environment variables are provided by Up:

  • PORT – port number such as “3000”

Header Injection

The headers object allows you to map HTTP header fields to paths. The most specific pattern takes precedence.

Here’s an example of two header fields specified for /* and /*.css:

{
  "name": "app",
  "type": "static",
  "headers": {
    "/*": {
      "X-Something": "I am applied to everything"
    },
    "/*.css": {
      "X-Something-Else": "I am applied to styles"
    }
  }
}

Requesting GET / will match the first pattern, injecting X-Something:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 200
Content-Type: text/html; charset=utf-8
Last-Modified: Fri, 21 Jul 2017 20:42:51 GMT
X-Powered-By: up
X-Something: I am applied to everything
Date: Mon, 31 Jul 2017 20:49:33 GMT

Requesting GET /style.css will match the second, more specific pattern, injecting X-Something-Else:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 50
Content-Type: text/css; charset=utf-8
Last-Modified: Fri, 21 Jul 2017 20:42:51 GMT
X-Powered-By: up
X-Something-Else: I am applied to styles
Date: Mon, 31 Jul 2017 20:49:35 GMT

Error Pages

By default Up will serve a minimalistic error page for you if the client accepts text/html, and it is explicitly enabled.

You may customize the default template’s color and optionally provide a support_email to allow customers to contact your support team.

{
  "name": "site",
  "type": "static",
  "error_pages": {
    "enable": true,
    "variables": {
      "support_email": "support@apex.sh",
      "color": "#228ae6"
    }
  }
}

If you’d like to provide custom templates you may create one or more of the following files. The most specific file takes precedence.

  • error.html – Matches any 4xx or 5xx
  • 5xx.html – Matches any 5xx error
  • 4xx.html – Matches any 4xx error
  • CODE.html – Matches a specific code such as 404.html

Variables specified via variables, as well as .StatusText and .StatusCode may be used in the template.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{.StatusText}} - {{.StatusCode}}</title>
    <link rel="stylesheet" href="/css/style.css">
  </head>
  <body>
    <h1>{{.StatusText}}</h1>
    {{with .Variables.support_email}}
      <span class="message">Please try your request again or <a href="mailto:{{.}}">contact support</a>.</span>
    {{else}}
      <span class="message">Please try your request again or contact support.</span>
    {{end}}
  </body>
</html>

Script Injection

Scripts, styles, and other tags may be injected to HTML pages before the closing </head> tag or closing </body> tag.

In the following example the <link rel="/style.css"> is injected to the head, as well as the inlining the scripts/config.js file. A <script src="/app.js"></script> is then injected into the body.

{
  "name": "site",
  "type": "static",
  "inject": {
    "head": [
      {
        "type": "style",
        "value": "/style.css"
      },
      {
        "type": "inline script",
        "file": "scripts/config.js"
      }
    ],
    "body": [
      {
        "type": "script",
        "value": "/app.js"
      }
    ]
  }
}

Currently you may specify the following types:

  • literal – A literal string
  • comment – An html comment
  • style – A style href
  • script – A script src
  • inline style – An inline style
  • inline script – An inline script
  • google analytics – Google Analytics snippet with API key
  • segment – Segment snippet with API key

All of these require a value, which sets the src, href, or inline content. Optionally you can populate value via a file path to a local file on disk, this is typically more convenient for inline scripts or styles. For example:

  • { "type": "literal", "value": "<meta name=...>" }
  • { "type": "comment", "value": "Just a boring comment" }
  • { "type": "script", "value": "/feedback.js" }
  • { "type": "style", "value": "/feedback.css" }
  • { "type": "inline script", "file": "/feedback.js" }
  • { "type": "inline style", "file": "/feedback.css" }
  • { "type": "script", "value": "var config = {};" }
  • { "type": "google analytics", "value": "API_KEY" }
  • { "type": "segment", "value": "API_KEY" }

Redirects and Rewrites

Up supports redirects and URL rewriting via the redirects object, which maps path patterns to a new location. If status is omitted (or 200) then it is a rewrite, otherwise it is a redirect.

{
  "name": "app",
  "type": "static",
  "redirects": {
    "/blog": {
      "location": "https://blog.apex.sh/",
      "status": 301
    },
    "/docs/:section/guides/:guide": {
      "location": "/help/:section/:guide",
      "status": 302
    },
    "/store/*": {
      "location": "/shop/:splat"
    }
  }
}

In the previous example /blog will redirect to a different site, while /docs/ping/guides/alerting will redirect to /help/ping/alerting. Finally /store/ferrets and nested paths such as /store/ferrets/tobi will redirect to /shop/ferrets/tobi and so on.

A common use-case for rewrites is for SPAs or Single Page Apps, where you want to serve the index.html file regardless of the path. The other common requirement for SPAs is that you of course can serve scripts and styles, so by default if a file is found, it will not be rewritten to location.

{
  "name": "app",
  "type": "static",
  "redirects": {
    "/*": {
      "location": "/",
      "status": 200
    }
  }
}

If you wish to force the rewrite regardless of a file existing, set force to true as shown here:

{
  "name": "app",
  "type": "static",
  "redirects": {
    "/*": {
      "location": "/",
      "status": 200,
      "force": true
    }
  }
}

Note that more specific target paths take precedence over those which are less specific, for example /blog will win over and /*.

Cross-Origin Resource Sharing

CORS is a mechanism which allows requests originating from a different host to make requests to your API. Several options are available to restrict this access, however to enable CORS for everyone all you need is to add:

{
  "cors": {
    "enable": true
  }
}

Suppose you have https://api.myapp.com, you may want to customize cors to allow access only from https://myapp.com:

{
  "cors": {
    "allowed_origins": ["https://myapp.com"],
    "allowed_methods": ["HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"],
    "allowed_headers": ["Content-Type", "Authorization"]
  }
}
  • allowed_origins – A list of origins a cross-domain request can be executed from. Use * to allow any origin, or a wildcard such as http://*.domain.com. Default is ["*"].
  • allowed_methods – A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (HEAD, GET, POST).
  • allowed_headers – A list of headers the client is allowed to use with cross-domain requests. If the special * value is present in the list, all headers will be allowed. Default is [].
  • exposed_headers – A list of headers which are safe to expose to the API of a CORS response.
  • max_age – A number indicating how long (in seconds) the results of a preflight request can be cached.
  • allow_credentials – A boolean indicating whether the request can include user credentials such as cookies, HTTP authentication or client side SSL certificates. Defaults to true.

Reverse Proxy

Up acts as a reverse proxy in front of your server, this is how CORS, redirection, script injection and other middleware style features are provided.

The following settings are available:

  • command – Command run through the shell to start your server (Default ./server)
    • When package.json is detected npm start is used
    • When app.js is detected node app.js is used
    • When app.py is detected python app.py is used
  • backoff – Backoff configuration object described in “Crash Recovery”
  • listen_timeout – Timeout in seconds Up will wait for your app to boot and listen on PORT (Default 15, Max 25)
  • shutdown_timeout – Timeout in seconds Up will wait after sending a SIGINT to your server, before sending a SIGKILL (Default 15)
{
  "proxy": {
    "command": "node app.js",
    "listen_timeout": 12,
    "shutdown_timeout": 5
  }
}

Crash Recovery

Another benefit of using Up as a reverse proxy is performing crash recovery. Up will retry idempotent requests upon failure, and upon crash it will restart your server and re-attempt before responding to the client.

By default the back-off is configured as:

  • min – Minimum time before retrying (Default 100ms)
  • max – Maximum time before retrying (Default 500ms)
  • factor – Factor applied to each attempt (Default 2)
  • attempts – Attempts made before failing (Default 3)
  • jitter – Apply jitter (Default false)

A total of 3 consecutive attempts will be made before responding with an error, in the default case this will be a total of 700ms for the three attempts.

Here’s an example tweaking the default behaviour:

{
  "proxy": {
    "command": "node app.js",
    "backoff": {
      "min": 500,
      "max": 1500,
      "factor": 1.5,
      "attempts": 5,
      "jitter": true
    }
  }
}

Since Up’s purpose is to proxy your http traffic, Up will treat network errors as a crash. When Up detects this, it will allow the server to cleanly close by sending a SIGINT, it the server does not close within proxy.shutdown_timeout seconds, it will forcibly close it with a SIGKILL.

DNS Zones & Records

Up allows you to configure DNS zones and records. One or more zones may be provided as keys in the dns object (“myapp.com” here), with a number of records defined within it.

{
  "name": "gh-polls",
  "dns": {
    "gh-polls.com": [
      {
        "name": "app.gh-polls.com",
        "type": "CNAME",
        "value": ["gh-polls.netlify.com"]
      }
    ]
  }
}

The record type must be one of:

  • A
  • AAAA
  • CNAME
  • MX
  • NAPTR
  • NS
  • PTR
  • SOA
  • SPF
  • SRV
  • TXT

Stages & Custom Domains

Up supports per-stage configuration, such as mapping of custom domains. Stage configuration is optional, this example maps all three to sub-domains:

{
  "stages": {
    "production": {
      "domain": "api.gh-polls.com"
    },
    "staging": {
      "domain": "stage.gh-polls.com"
    }
    "development": {
      "domain": "dev.gh-polls.com"
    }
  }
}

This example maps only the production stage:

{
  "stages": {
    "production": {
      "domain": "gh-polls.com"
    }
  }
}

Plan the changes via up stack plan and up stack apply to perform the changes. Note that CloudFront can take up to ~40 minutes to distribute this configuration globally, so grab a coffee while these changes are applied.

Custom stages may be supported in the future, for now there are three:

  • development – The latest deployment
  • staging – The latest deployment specific to “staging”
  • production – The latest deployment specific to “production”

If you’re familiar with AWS Lambda, “development” is the $LATEST version, while “staging” and “production” are aliases.

You may purchase domains from the command-line, or map custom domains from other registrars. Up uses Route53 to purchase domains using your AWS account credit card. See up help domains.

Ignoring Files

Up supports gitignore style pattern matching for omitting files from deployment. The following files are loaded in sequence:

  • .gitignore
  • .upignore

An example .upignore to omit markdown and .go source files might look like this:

*.md
*.go

Negation

By default dotfiles are ignored, if you wish to include them, you may use ! to negate a pattern in .upignore:

!.myfile

Another use-case for negation is to ignore everything and explicitly include a number of files instead, to be more specific:

*
!app.js
!package.json
!node_modules/**
!src/**

Inspecting

To get a better idea of which files are being filtered or added, use up -v when deploying, and you may also find it useful to grep in some cases:

$ up -v 2>&1 | grep filtered
DEBU filtered .babelrc – 25
DEBU filtered .git – 408
DEBU filtered .gitignore – 13
DEBU filtered node_modules/ansi-regex/readme.md – 1749
DEBU filtered node_modules/ansi-styles/readme.md – 1448
DEBU filtered node_modules/binary-extensions/readme.md – 751
DEBU filtered node_modules/chalk/readme.md – 6136

You may also wish to use up build --size to view the largest files within the zip.

Node specifics

When building a Node project, node_modules is often added to .gitignore, so Up special-cases this scenario so that node_modules is not excluded by default.

If you’re using a bundler such as Webpack or Browserify instead, you may wish to ignore node_modules as they’re not required in production, add this to your ./.upignore:

node_modules

Pattern matching

Note that patterns are matched much like .gitignore, so if you have the following .upignore contents even node_modules/debug/src/index.js will be ignored since it contains src.

src

You can be more specific with a leading ./:

./src

Files can be matched recursively using **, for example ignoring everything except the files in dist:

*
!dist/**

Runtimes

Up supports a number of interpreted languages, and virtually any language which can be compiled to a binary such as Golang. Up does its best to provide idiomatic and useful out-of-the-box experiences tailored to each language. Currently first-class support is provided for:

  • Python
  • Golang
  • Node.js
  • Crystal
  • Static sites

Node.js

When a package.json file is detected, Node.js is the assumed runtime. By default the latest version supported by Lambda is used (6.10). If you’d like to deploy other versions of Node you may want to consider using the Node binaries.

The build hook becomes:

$ npm run build

The server run by the proxy becomes:

$ npm start

Python

When requirements.txt is present the build command becomes:

$ mkdir -p .pypath/ && pip install -r requirements.txt -t .pypath/

The server run by the proxy becomes:

$ python app.py

Golang

When a main.go file is detected, Golang is the assumed runtime.

The build hook becomes:

$ GOOS=linux GOARCH=amd64 go build -o server *.go

The clean hook becomes:

$ rm server

Crystal

When a main.cr file is detected, Crystal is the assumed runtime. Note that this runtime requires Docker to be installed.

The build hook becomes:

$ docker run --rm -v $(PWD):/src -w /src tjholowaychuk/up-crystal crystal build --link-flags -static -o server main.cr

The clean hook becomes:

$ rm server

Static

When an index.html file is detected the project is assumed to be static.

Commands

Up provides the up command-line program, used to deploy the app, and manage associated resources such as domains and SSL certificates, as well as operational tasks like viewing logs.

To view details for a command at any time use up help or up help <command>.

Usage:

  up [<flags>] <command> [<args> ...]

Flags:

  -h, --help           Output usage information.
  -r, --region=REGION  Override the region.
  -C, --chdir="."      Change working directory.
  -v, --verbose        Enable verbose log output.
      --version        Show application version.

Commands:

  help            Show help for a command.
  build           Build zip file.
  config          Show configuration after defaults and validation.
  deploy          Deploy the project.
  domains list    List purchased domains.
  domains check   Check availability of a domain.
  domains buy     Purchase a domain.
  logs            Show log output.
  metrics         Show project metrics.
  run             Run a hook.
  stack plan      Plan configuration changes.
  stack apply     Apply configuration changes.
  stack delete    Delete configured resources.
  stack status    Show status of resources.
  start           Start development server.
  upgrade         Install the latest release of Up.
  url             Show, open, or copy a stage endpoint.
  version         Show version.

Deploy

Deploy the project, by default to the “development” stage. Note that running up and up deploy are identical, however for staging and production you must run up deploy <stage>.

Usage:

  up deploy [<stage>]

Flags:

  -h, --help           Output usage information.
  -r, --region=REGION  Override the region.
  -C, --chdir="."      Change working directory.
  -v, --verbose        Enable verbose log output.
      --version        Show application version.

Args:

  [<stage>]  Target stage name.

Examples

Deploy the project to the development stage.

$ up

Deploy the project to the development stage, this is the same as running up without arguments.

$ up deploy

Deploy the project to the staging stage.

$ up deploy staging

Deploy the project to the production stage.

$ up deploy production

Config

Validate and output configuration with defaults applied.

$ up config
{
  "name": "app",
  "description": "",
  "type": "server",
  "headers": null,
  "redirects": null,
  "hooks": {
    "build": "GOOS=linux GOARCH=amd64 go build -o server *.go",
    "clean": "rm server"
  },
  "environment": null,
  "regions": [
    "us-west-2"
  ],
  "inject": null,
  "lambda": {
    "role": "arn:aws:iam::ACCOUNT:role/lambda_function",
    "memory": 128,
    "timeout": 5
  },
  "cors": null,
  "error_pages": {
    "dir": ".",
    "variables": null
  },
  "proxy": {
    "command": "./server",
    "backoff": {
      "min": 100,
      "max": 500,
      "factor": 2,
      "attempts": 3,
      "jitter": false
    }
  },
  "static": {
    "dir": "."
  },
  "logs": {
    "disable": false
  },
  "dns": {
    "zones": null
  }
}
...

Logs

Show or tail log output with optional query for filtering. When viewing or tailing logs, you are viewing them from all stages, see the examples below to filter on a stage name.

 Usage:

   up logs [<flags>] [<query>]

 Flags:

   -h, --help           Output usage information.
   -r, --region=REGION  Override the region.
   -C, --chdir="."      Change working directory.
   -v, --verbose        Enable verbose log output.
       --version        Show application version.
   -f, --follow         Follow or tail the live logs.

 Args:

   [<query>]  Query pattern for filtering logs.

JSON Output

When stdout is not a terminal Up will output the logs as JSON, which can be useful for further processing with tools such as jq.

In this contrived example the last 5 hours of production errors are piped to jq to produce a CSV of HTTP methods to IP address.

$ up logs -s 5h 'production error' | jq -r '.|[.fields.method,.fields.ip]|@csv'

Yielding:

"GET","207.194.34.24"
"GET","207.194.34.24"
"GET","207.194.34.24"

Examples

Show logs from the past 5 minutes.

$ up logs

Show logs from the past 45 minutes.

$ up -s 45m logs

Show logs from the past 24 hours.

$ up -s 24h logs

Show live log output.

$ up logs -f

Show live logs from production only.

$ up logs -f production

Show live error logs from production only.

$ up logs -f 'production error'

Show error logs, which include 5xx responses.

$ up logs error

Show error and warning logs, which include 4xx and 5xx responses.

$ up logs 'warn or error'

Show logs with a specific message.

$ up logs 'message = "user login"'

Show responses with latency above 15ms.

$ up logs 'duration > 15'

Show 4xx and 5xx responses in production

$ up logs 'production (warn or error)'

Show production 5xx responses with a POST, PUT, or DELETE method.

$ up logs 'production error method in ("POST", "PUT", "DELETE")

Show 200 responses with latency above 1500ms.

$ up logs 'status = 200 duration > 1.5s'

Show responses with bodies larger than 100kb.

$ up logs 'size > 100kb'

Show 4xx and 5xx responses.

$ up logs 'status >= 400'

Show emails containing @apex.sh.

$ up logs 'user.email contains "@apex.sh"'

Show emails ending with @apex.sh.

$ up logs 'user.email = "*@apex.sh"'

Show emails starting with tj@.

$ up logs 'user.email = "tj@*"'

Show logs with a more complex query.

$ up logs 'method in ("POST", "PUT") ip = "207.*" status = 200 duration >= 50'

URL

Show, open, or copy a stage endpoint.

Usage:

  up url [<flags>] [<stage>]

Flags:

  -h, --help           Output usage information.
  -r, --region=REGION  Override the region.
  -C, --chdir="."      Change working directory.
  -v, --verbose        Enable verbose log output.
      --version        Show application version.
  -o, --open           Open endpoint in the browser.
  -c, --copy           Copy endpoint to the clipboard.

Args:

  [<stage>]  Name of the stage.

Examples

Show the development endpoint.

$ up url

Open the development endpoint in the browser.

$ up url --open

Copy the development endpoint to the clipboard.

$ up url --copy

Show the production endpoint.

$ up url production

Open the production endpoint in the browser.

$ up url -o production

Copy the production endpoint to the clipboard.

$ up url -c production

Start

Start development server. The development server runs the same proxy that is used in production for serving, so you can test a static site or application locally with the same feature-set.

Currently up start does not work with cross-compiled languages such as Go or Crystal.

Usage:

  up start [<flags>]

Flags:

  -h, --help             Output usage information.
  -r, --region=REGION    Override the region.
  -C, --chdir="."        Change working directory.
  -v, --verbose          Enable verbose log output.
      --version          Show application version.
      --address=":3000"  Address for server.

Examples

Start development server on port 3000.

$ up start

Start development server on port 5000.

$ up start --address :5000

Stack

Stack resource management. The stack is essentially all of the resources powering your app, which is configured by Up on the first deploy.

At any time if you’d like to delete the application simply run $ up stack delete. To view the status and potential errors use $ up stack.

Usage:

  up stack <command> [<args> ...]

Flags:

  -h, --help           Output usage information.
  -r, --region=REGION  Override the region.
  -C, --chdir="."      Change working directory.
  -v, --verbose        Enable verbose log output.
      --version        Show application version.

Subcommands:
  stack plan      Plan configuration changes.
  stack apply     Apply configuration changes.
  stack delete    Delete configured resources.
  stack status    Show status of resources.

Examples

Show status of the stack resources.

$ up stack

Show resource changes.

$ up stack plan

Apply resource changes.

$ up stack apply

Delete the stack resources.

$ up stack delete

Build

Build zip file, typically only helpful for inspecting its contents. If you’re interested in seeing what files are causing bloat, use the --size flag to list files by size descending.

Usage:

  up build [<flags>]

Flags:

  -h, --help           Output usage information.
  -r, --region=REGION  Override the region.
  -C, --chdir="."      Change working directory.
  -v, --verbose        Enable verbose log output.
      --version        Show application version.
      --size           Show zip contents size information.

Examples

Build archive and save to ./out.zip

$ up build

Build archive and output to file via stdout.

$ up build > /tmp/out.zip

Build archive list files by size.

$ up build --size

Build archive and list size without creating out.zip.

$ up build --size > /dev/null

Guides

Development to Production Workflow

This section guides you through taking a small application from development, to production, complete with purchasing and mapping a custom domain.

Deploying

First create app.js in an empty directory with the following Node.js app. Note that it must listen on PORT which is passed by Up.

const http = require('http')
const { PORT = 3000 } = process.env

http.createServer((req, res) => {
  res.end('Hello World\n')
}).listen(PORT)

Next you should give your application a name and start configuring. The profile name should correspond with the name in ~/.aws/credentials so that Up knows which AWS account to deploy to, and which credentials to use.

{
  "name": "up-example",
  "profile": "up-tobi"
}

Run up to deploy the application.

$ up

   build: 5 files, 3.9 MB (358ms)
  deploy: complete (14.376s)
   stack: complete (1m12.086s)

Test with curl to ensure everything is working:

$ curl `up url`
Hello World

Purchasing a Domain

Domains can be mapped from existing services, or purchased directly from AWS via Route53. First check if the domain you’d like is available:

$ up domains check up.com

  Domain up.com is unavailable

  Suggestions:

  theupwards.com          $12.00 USD
  upwardonline.com        $12.00 USD
  myupwards.com           $12.00 USD
  theastir.com            $12.00 USD
  astironline.com         $12.00 USD
  myastir.com             $12.00 USD
  myupward.net            $11.00 USD
  cleanup.tv              $32.00 USD
  myup.tv                 $32.00 USD
  itup.tv                 $32.00 USD
  newup.tv                $32.00 USD
  thedown.net             $11.00 USD
  theupward.net           $11.00 USD
  upwardsonline.net       $11.00 USD

Oh no up.com is taken! Try another:

$ up domains check up-example.com

  Domain up-example.com is available for $12.00 USD

Purchase it with the following command, and fill out the details required by the registrar.

$ up domains buy up-example.com

  Confirm domain: up-example.com
  First name: TJ
  Last name: Holowaychuk
  Email: tj@apex.sh
  Phone: +1.2501007000
  Country code: CA
  City: Victoria
  State or province: BC
  Zip code: X9X 9X9
  Address: Some address here

It can take a few minutes for AWS to finalize the purchase, you should receive an email, then you’ll see it in the up domains output, along with the automatic renewal time.

$ up domains

  gh-polls.com             renews Aug 28 17:17:58
  up-example.com           renews Sep 19 19:40:50

Deploying to Stages

Before deploying to the staging and production stages, first tweak the application a little to include the UP_STAGE environment variable:

const http = require('http')
const { PORT = 3000, UP_STAGE } = process.env

http.createServer((req, res) => {
  res.end('Hello World from ' + UP_STAGE)
}).listen(PORT)

Now deploy to development and production. Note that up is an alias of up deploy development.

$ up
$ up deploy production

Open both in the browser:

$ up url -o
$ up url production -o

You should see “Hello World from production” and “Hello World from development”.

Mapping Custom Domains to Stages

Now that you have an application deployed, you probably want a fancy custom domain for it right? You can map these using the stages and domain properties.

Here we let Up know that we want up-example.com for production and dev.up-example for development. You could also map staging to stage.up-example.com or similar if you’d like.

{
  "name": "up-example",
  "profile": "up-tobi",
  "stages": {
    "development": {
      "domain": "dev.up-example.com"
    },
    "production": {
      "domain": "up-example.com"
    }
  }
}

Note that you could map staging to a domain like staging-myapp.com as well, it does not have to be a sub-domain of your production domain.

Now when you run up stack plan to preview changes to your resources, it will prompt you to verify the Let’s Encrypt certificate emails that AWS sends.

$ up stack plan

       domains: Verify your email
     ⠧ confirm: up-example.com

AWS requires email verification to prove you own the domain. After clicking “I Approve” in the email, the output will resume and you’ll see some new resources Up will be creating.

Add AWS::ApiGateway::DomainName
  id: ApiDomainDevelopment

Add AWS::ApiGateway::BasePathMapping
  id: ApiDomainDevelopmentPathMapping

Add AWS::ApiGateway::DomainName
  id: ApiDomainProduction

Add AWS::ApiGateway::BasePathMapping
  id: ApiDomainProductionPathMapping

Add AWS::Route53::RecordSet
  id: DnsZoneDevUpExampleComRecordDevUpExampleCom

Add AWS::Route53::RecordSet
  id: DnsZoneUpExampleComRecordUpExampleCom

If you’re curious, now that Up knows you want to map the domain(s), it will create:

  • Registers ACM free SSL certificate(s) for your domain(s)
  • CloudFront distribution for the API Gateway
  • API Gateway stage mapping
  • Route53 DNS zone and record(s) mapping to the CloudFront distribution

Now apply these changes:

$ up stack apply

After the changes have been applied, it can take roughly 10-40 minutes for CloudFront to distribute the configuration and SSL certificate globally, so until then our up-example.com domain won’t work.

Once available https://up-example.com will always point to production via up deploy production, and https://dev.up-example.com/ will point to the latest deployment via up.

Stack Changes

The “stack” is all of the resources associated with your app. You plan changes via up stack plan and perform them with up stack apply.

Suppose you wanted to map the “staging” stage, you would first add it to up.json:

{
  "name": "up-example",
  "profile": "up-tobi",
  "stages": {
    "development": {
      "domain": "dev.up-example.com"
    },
    "staging": {
      "domain": "stage.up-example.com"
    },
    "production": {
      "domain": "up-example.com"
    }
  }
}

Then run:

$ up stack plan

Review the output, it should be all “Add”s in this case, then apply:

$ up stack apply

Deleting the App

After you’re done messing around, you may want to remove all the resources and the app itself. To do so simply run:

$ up stack delete

Logging

This section describes how you can log from you application in a way that Up will recognize. In the future Up will support forwarding your logs to services such as Loggly, Papertrail or ELK.

Plain Text

The first option is plain-text logs to stdout or stderr. Currently writes to stderr are considered ERROR-level logs, and stdout becomes INFO.

Writing plain-text logs is simple, for example with Node.js:

console.log('User signed in')
console.error('Failed to sign in: %s', err)

Would be collected as:

 INFO: User signed in
ERROR: Failed to sign in: something broke

Multi-line indented logs are also supported, and are treated as a single message. For example:

console.log('User signed in')
console.log('  name: %s', user.name)
console.log('  email: %s', user.email)

Would be collected as the single entry:

INFO: User signed in
  name: tj
  email: tj@apex.sh

This feature is especially useful for stack traces.

JSON

The second option is structured logging with JSON events, which is preferred as it allows you to query against specific fields and treat logs like events.

JSON logs require a level and message field:

console.log(`{ "level": "info", "message": "User signin" }`)

Would be collected as:

INFO: User login

The message field should typically contain no dynamic content, such as user names or emails, these can be provided as fields:

console.log(`{ "level": "info", "message": "User login", "fields": { "name": "Tobi", "email": "tobi@apex.sh" } }`)

Would be collected as:

INFO: User login name=Tobi email=tobi@apex.sh

Allowing you to perform queries such as:

$ up logs 'message = "User login" name = "Tobi"'

Or:

$ up logs 'name = "Tobi" or email = "tobi@*"'

Here’s a simple JavaScript logger for reference, all you need to do is output some JSON to stdout and Up will handle the rest!

function log(level, message, fields = {}) {
  const entry = { level, message, fields }
  console.log(JSON.stringify(entry))
}

Log Query Language

Up supports a comprehensive query language, allowing you to perform complex filters against structured data, supporting operators, equality, substring tests and so on. This section details the options available when querying.

AND Operator

The and operator is implied, and entirely optional to specify, since this is the common case.

Suppose you have the following example query to show only production errors from a the specified IP address.

production error ip = "207.194.32.30"

The parser will inject and, effectively compiling to:

production and error and ip = "207.194.38.50"

Or Operator

There is of course also an or operator, for example showing warnings or errors.

production (warn or error)

These may of course be nested as you require:

(production or staging) (warn or error) method = "GET"

Equality Operators

The = and != equality operators allow you to filter on the contents of a field.

Here = is used to show only GET requests:

method = "GET"

Or for example != may be used to show anything except GET:

method != "GET"

Relational Operators

The >, >=, <, and <= relational operators are useful for comparing numeric values, for example response status codes:

status >= 200 status < 300

Stages

Currently all development, staging, and production logs are all stored in the same location, however you may filter to find exactly what you need.

The keywords production, staging, and development expand to:

stage = "production"

For example filtering on slow production responses:

production duration >= 1s

Is the same as:

stage = "production" duration >= 1s

Severity Levels

Up provides request level logging with severity levels applied automatically, for example a 5xx response is an ERROR level, while 4xx is a WARN, and 3xx or 2xx are the INFO level.

This means that instead of using the following for showing production errors:

production status >= 500

You may use:

production error

In Operator

The in operator checks for the presence of a field within the set provided. For example showing only POST, PUT and PATCH requests:

method in ("POST", "PUT", "PATCH")

Not Operator

The not operator is a low-precedence negation operator, for example excluding requests with the method POST, PUT, or PATCH:

not method in ("POST", "PUT", "PATCH")

Since it is the lowest precedence operator, the following will show messages that are not “user login” or “user logout”:

not message = "user login" or message = "user logout"

Effectively compiling to:

!(message = "user login" or message = "user logout")

Units

The log grammar supports units for bytes and durations, for example showing responses larger than 56kb:

size > 56kb

Or showing responses longer than 1500ms:

duration > 1.5s

Byte units are:

  • b bytes (123b or 123 are equivalent)
  • kb bytes (5kb, 128kb)
  • mb bytes (5mb, 15.5mb)

Duration units are:

  • ms milliseconds (100ms or 100 are equivalent)
  • s seconds (1.5s, 5s)

Substring Matches

When filtering on strings, such as the log message, you may use the * character for substring matches.

For example if you want to show logs with a remote ip prefix of 207.:

ip = "207.*"

Or a message containing the word “login”:

message = "*login*"

There is also a special keyword for this case:

message contains "login"

FAQ

Is this a hosted service?

There are currently no plans for a hosted version. Up lets you deploy applications to your own AWS account for isolation, security, and longevity, don’t worry about a startup going out of business.

What platforms does Up support?

Currently AWS via API Gateway and Lambda are supported, this is the focus until Up is nearing feature completion, after which additional providers such as GCP and Azure will be added.

How is this different than other serverless frameworks?

Most of the AWS Lambda based tools are function-oriented, while Up abstracts this away entirely. Up does not use framework “shims”, the servers that you run using Up are regular HTTP servers and require no code changes for Lambda compatibility.

Up keeps your apps and APIs portable, makes testing them locally easier, and prevents vendor lock-in. The Lambda support for Up is simply an implementation detail, you are not coupled to API Gateway or Lambda. Up uses the API Gateway proxy mode to send all requests (regardless of path or method) to your application.

If you’re looking to manage function-level event processing pipelines, Apex or Serverless are likely better candidates, however if you’re creating applications, apis, micro services, or websites, Up is built for you.

Why run HTTP servers in Lambda?

You might be thinking this defeats the purpose of Lambda, however most people just want to use the tools they know and love. Up lets you be productive developing locally as you normally would, Lambda for hosting is only an implementation detail.

With Up you can use any Python, Node, Go, or Java framework you’d normally use to develop, and deploy with a single command, while maintaining the cost effectiveness, self-healing, and scaling capabilities of Lambda.

How much does it cost to run an application?

AWS API Gateway provides 1 million free requests per month, so there’s a good chance you won’t have to pay anything at all. Beyond that view the AWS Pricing for more information.

How well does it scale?

Up scales to fit your traffic on-demand, you don’t have to do anything beyond deploying your code. There’s no restriction on the number of concurrent instances, apps, custom domains and so on.

How much latency does Up’s reverse proxy introduce?

With a 512mb Lambda function Up introduces an average of around 500µs (microseconds) per request.

Do the servers stay active while idle?

This depends on the platform, and with Lambda being the initial platform provided the current answer is no, the server(s) are frozen when inactive and are otherwise “stateless”.

Typically relying on background work in-process is an anti-pattern as it does not scale. Lambda functions combined with CloudWatch scheduled events for example are a good way to handle this kind of work, if you’re looking for a scalable alternative.

Why is Up licensed as GPLv3?

Up is licensed in such a way that myself as an independent developer can continue to improve the product and provide support. Commercial customers receive access to a premium version of Up with additional features, priority support for bugfixes, and of course knowing that the project will stick around! Up saves your team countless hours maintaining infrastructure and custom tooling, so you can get back to what makes your company and products unique.

Can I donate?

Yes you can! Head over to the OpenCollective page. Any donations are greatly appreciated and help me focus more on Up’s implementation, documentation, and examples. If you’re using the free OSS version for personal or commercial use please consider giving back, even a few bucks buys a coffee :).