diff --git a/demos/guestbook/add.py b/demos/guestbook/add.py
new file mode 100644
index 00000000..5e923457
--- /dev/null
+++ b/demos/guestbook/add.py
@@ -0,0 +1,16 @@
+#
+# Handles POST /guestbook -- adds item to guestbook
+#
+
+from flask import request, redirect
+import redis
+
+# Connect to redis.
+redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
+
+def main():
+ # Read the item from POST params, add it to redis, and redirect
+ # back to the list
+ item = request.form['text']
+ redisConnection.rpush('guestbook', item)
+ return redirect('/guestbook', code=303)
diff --git a/demos/guestbook/cleanup.sh b/demos/guestbook/cleanup.sh
new file mode 100755
index 00000000..62f7db9b
--- /dev/null
+++ b/demos/guestbook/cleanup.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+kubectl delete ns guestbook
+
+fission fn delete --name guestbook-add
+fission fn delete --name guestbook-get
+
+fission route list|grep guestbook|cut -f1 -d' '|xargs -n1 fission route delete --name
diff --git a/demos/guestbook/get.py b/demos/guestbook/get.py
new file mode 100644
index 00000000..a95016b4
--- /dev/null
+++ b/demos/guestbook/get.py
@@ -0,0 +1,28 @@
+#
+# Handles GET /guestbook -- returns a list of items in the guestbook
+# with a form to add more.
+#
+
+from flask import current_app, escape
+import redis
+
+# Connect to redis. This is run only when this file is loaded; as
+# long as the pod is alive, the connection is reused.
+redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
+
+def main():
+ messages = redisConnection.lrange('guestbook', 0, -1)
+
+ items = [("
%s" % escape(m.decode('utf-8'))) for m in messages]
+ ul = "" % "\n".join(items)
+ return """
+
+ Guestbook
+
+
+ %s
+
+ """ % ul
diff --git a/demos/guestbook/redis.yaml b/demos/guestbook/redis.yaml
new file mode 100644
index 00000000..786693e2
--- /dev/null
+++ b/demos/guestbook/redis.yaml
@@ -0,0 +1,45 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: guestbook
+ labels:
+ name: guestbook
+
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ labels:
+ run: redis
+ name: redis
+ namespace: guestbook
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ run: redis
+ template:
+ metadata:
+ labels:
+ run: redis
+ spec:
+ containers:
+ - image: redis
+ name: redis
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ run: redis
+ name: redis
+ namespace: guestbook
+spec:
+ selector:
+ run: redis
+ type: ClusterIP
+ ports:
+ - port: 6379
+ protocol: TCP
+ targetPort: 6379
diff --git a/demos/guestbook/run.sh b/demos/guestbook/run.sh
new file mode 100755
index 00000000..5196edc1
--- /dev/null
+++ b/demos/guestbook/run.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+DEMO_RUN_FAST=1
+ROOT_DIR=$(dirname $0)/..
+. $ROOT_DIR/util.sh
+
+desc "Deploy redis"
+run "kubectl create -f redis.yaml"
+
+desc "Ensure fission has a python environment"
+run "fission env create --name python --image fission/python-env"
+
+desc "Register fission functions"
+run "fission function create --name guestbook-get --env python --code get.py --url /guestbook --method GET"
+run "fission function create --name guestbook-add --env python --code add.py --url /guestbook --method POST"
+
+echo "http://$FISSION_ROUTER/guestbook"
+
diff --git a/demos/hello/cleanup.sh b/demos/hello/cleanup.sh
new file mode 100755
index 00000000..07c292a3
--- /dev/null
+++ b/demos/hello/cleanup.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+fission fn delete --name hello
+fission route delete --name $(fission route list|grep hello|cut -f1 -d' ')
diff --git a/demos/hello/hello.js b/demos/hello/hello.js
new file mode 100644
index 00000000..4bcbf4f8
--- /dev/null
+++ b/demos/hello/hello.js
@@ -0,0 +1,7 @@
+
+module.exports = async function(context) {
+ return {
+ status: 200,
+ body: "Hello, world!\n"
+ };
+}
diff --git a/demos/hello/run.sh b/demos/hello/run.sh
new file mode 100755
index 00000000..bc4f6860
--- /dev/null
+++ b/demos/hello/run.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+DEMO_RUN_FAST=1
+ROOT_DIR=$(dirname $0)/..
+. $ROOT_DIR/util.sh
+
+desc "Kubernetes cluster"
+run "kubectl get nodes"
+
+desc "Fission installed"
+run "kubectl --namespace fission get deployment"
+
+clear
+
+desc "Hello world function"
+run "cat hello.js"
+
+desc "Add NodeJS environment to fission"
+run "fission env create --name nodejs --image fission/node-env"
+
+desc "NodeJS environment pods"
+run "kubectl --namespace fission-function get pod -l environmentName=nodejs"
+
+desc "Upload a function to fission"
+run "fission function create --name hello --env nodejs --code hello.js"
+
+desc "Set up a route \(HTTP trigger\) for the function"
+run "fission route create --method GET --url /hello --function hello"
+
+sleep 2
+
+desc "Finally, run the function"
+run "time -p curl http://$FISSION_ROUTER/hello"
+
+desc "Run the function again"
+run "time -p curl http://$FISSION_ROUTER/hello"
+run "time -p curl http://$FISSION_ROUTER/hello"
+
+desc "The pod is now labeled by the function name"
+run "kubectl --namespace fission-function get pod -l functionName=hello"
diff --git a/demos/util.sh b/demos/util.sh
new file mode 100644
index 00000000..e1a08504
--- /dev/null
+++ b/demos/util.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# Copyright 2016 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+readonly reset=$(tput sgr0)
+readonly green=$(tput bold; tput setaf 2)
+readonly yellow=$(tput bold; tput setaf 3)
+readonly blue=$(tput bold; tput setaf 6)
+readonly timeout=$(if [ "$(uname)" == "Darwin" ]; then echo "1"; else echo "0.1"; fi)
+
+function desc() {
+ maybe_first_prompt
+ echo "$blue# $@$reset"
+ prompt
+}
+
+function prompt() {
+ echo -n "$yellow\$ $reset"
+}
+
+started=""
+function maybe_first_prompt() {
+ if [ -z "$started" ]; then
+ prompt
+ started=true
+ fi
+}
+
+# After a `run` this variable will hold the stdout of the command that was run.
+# If the command was interactive, this will likely be garbage.
+#DEMO_RUN_STDOUT=""
+
+function run() {
+ maybe_first_prompt
+ rate=25
+ if [ -n "$DEMO_RUN_FAST" ]; then
+ rate=1000
+ fi
+ echo "$green$1$reset" | pv -qL $rate
+ if [ -n "$DEMO_RUN_FAST" ]; then
+ sleep 0.5
+ fi
+ $1
+ echo; echo -n ">"
+ read -s
+ echo -e "\b "
+# OFILE="$(mktemp -t $(basename $0).XXXXXX)"
+# script -q -t0 "$OFILE" $1 # [removed -e,-c options -soam]
+# r=$?
+# read -d '' -t "${timeout}" -n 10000 # clear stdin
+# prompt
+# if [ -z "$DEMO_AUTO_RUN" ]; then
+# read -s
+# fi
+# DEMO_RUN_STDOUT="$(tail -n +2 $OFILE | sed 's/\r//g')"
+# return $r
+}
+
+function relative() {
+ for arg; do
+ echo "$(realpath $(dirname $(which $0)))/$arg" | sed "s|$(realpath $(pwd))|.|"
+ done
+}
+
+SSH_NODE=$(kubectl get nodes | tail -1 | cut -f1 -d' ')
+
+#trap "echo" EXIT