diff --git a/aws-ts-containers/Pulumi.yaml b/aws-ts-containers/Pulumi.yaml
new file mode 100644
index 000000000..518a93bd6
--- /dev/null
+++ b/aws-ts-containers/Pulumi.yaml
@@ -0,0 +1,8 @@
+name: container-quickstart
+description: NGINX container example
+runtime: nodejs
+template:
+ config:
+ aws:region:
+ description: The AWS region to deploy into
+ default: us-west-2
diff --git a/aws-ts-containers/README.md b/aws-ts-containers/README.md
new file mode 100644
index 000000000..3dcc288d7
--- /dev/null
+++ b/aws-ts-containers/README.md
@@ -0,0 +1,65 @@
+[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)
+
+# Easy container example
+
+Companion to the tutorial [Provision containers on AWS](https://pulumi.io/quickstart/aws-containers.html).
+
+## Prerequisites
+
+To run this example, make sure [Docker](https://docs.docker.com/engine/installation/) is installed and running.
+
+## Running the App
+
+Note: some values in this example will be different from run to run. These values are indicated
+with `***`.
+
+1. Create a new stack:
+
+ ```
+ $ pulumi stack init containers-dev
+ ```
+
+1. Configure Pulumi to use an AWS region that supports Fargate. This is currently only available in `us-east-1`, `us-east-2`, `us-west-2`, and `eu-west-1`:
+
+ ```
+ $ pulumi config set aws:region us-west-2
+ ```
+
+1. Restore NPM modules via `npm install` or `yarn install`.
+
+1. Preview and deploy the app via `pulumi up`. The preview will take a few minutes, as it builds a Docker container. A total of 19 resources are created.
+
+ ```
+ $ pulumi up
+ ```
+
+1. View the endpoint URL, and run curl:
+
+ ```bash
+ $ pulumi stack output
+ Current stack outputs (1)
+ OUTPUT VALUE
+ hostname http://***.elb.us-west-2.amazonaws.com
+
+ $ curl $(pulumi stack output hostname)
+
+
+
\ No newline at end of file
diff --git a/aws-ts-containers/index.ts b/aws-ts-containers/index.ts
new file mode 100644
index 000000000..8ff8af032
--- /dev/null
+++ b/aws-ts-containers/index.ts
@@ -0,0 +1,24 @@
+import * as awsx from "@pulumi/aws-infra";
+
+// Create an elastic network listener to listen for requests and route them to the container.
+// See https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html
+// for more details.
+let listener = new awsx.elasticloadbalancingv2.NetworkListener("nginx", { port: 80 });
+
+// Define the service to run. We pass in the listener to hook up the network load balancer
+// to the containers the service will launch.
+let service = new awsx.ecs.FargateService("nginx", {
+ desiredCount: 2,
+ taskDefinitionArgs: {
+ containers: {
+ nginx: {
+ image: awsx.ecs.Image.fromPath("./app"),
+ memory: 512,
+ portMappings: [listener],
+ },
+ },
+ },
+});
+
+// export just the hostname property of the container frontend
+export const hostname = listener.endpoint().apply(e => `http://${e.hostname}`);
diff --git a/aws-ts-containers/package.json b/aws-ts-containers/package.json
new file mode 100644
index 000000000..e59736feb
--- /dev/null
+++ b/aws-ts-containers/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "container-quickstart",
+ "main": "index.js",
+ "dependencies": {
+ "@pulumi/pulumi": "dev",
+ "@pulumi/aws": "dev",
+ "@pulumi/aws-infra": "=0.16.3-dev.1546999531"
+ }
+}
diff --git a/aws-ts-voting-app/Pulumi.yaml b/aws-ts-voting-app/Pulumi.yaml
new file mode 100644
index 000000000..620e31c4d
--- /dev/null
+++ b/aws-ts-voting-app/Pulumi.yaml
@@ -0,0 +1,11 @@
+name: voting-app
+runtime: nodejs
+description: Voting app that uses containers
+template:
+ config:
+ aws:region:
+ description: The AWS region to deploy into
+ default: us-west-2
+ redisPassword:
+ description: The Redis password
+ secret: true
diff --git a/aws-ts-voting-app/README.md b/aws-ts-voting-app/README.md
new file mode 100644
index 000000000..4afacd222
--- /dev/null
+++ b/aws-ts-voting-app/README.md
@@ -0,0 +1,111 @@
+[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)
+
+# Voting app with two containers
+
+A simple voting app that uses Redis for a data store and a Python Flask app for the frontend. The example has been ported from https://github.com/Azure-Samples/azure-voting-app-redis.
+
+The example shows how easy it is to deploy containers into production and to connect them to one another. Since the example defines a custom container, Pulumi does the following:
+- Builds the Docker image
+- Provisions AWS Container Registry (ECR) instance
+- Pushes the image to the ECR instance
+- Creates a new ECS task definition, pointing to the ECR image definition
+
+## Prerequisites
+
+To use this example, make sure [Docker](https://docs.docker.com/engine/installation/) is installed and running.
+
+## Deploying and running the program
+
+### Configure the deployment
+
+Note: some values in this example will be different from run to run. These values are indicated
+with `***`.
+
+1. Login via `pulumi login`.
+
+1. Create a new stack:
+
+ ```
+ $ pulumi stack init voting-app-testing
+ ```
+
+1. Set AWS as the provider:
+
+ ```
+ $ pulumi config set cloud:provider aws
+ ```
+
+1. Configure Pulumi to use an AWS region that supports Fargate, which is currently only available in `us-east-1`, `us-east-2`, `us-west-2`, and `eu-west-1`:
+
+ ```
+ $ pulumi config set aws:region us-west-2
+ ```
+
+1. Set a value for the Redis password. The value can be an encrypted secret, specified with the `--secret` flag. If this flag is not provided, the value will be saved as plaintext in `Pulumi.testing.yaml` (since `testing` is the current stack name).
+
+ ```
+ $ pulumi config set --secret redisPassword S3cr37Password
+ ```
+
+### Install dependencies
+
+1. Restore NPM modules via `npm install` or `yarn install`.
+
+### Preview and deploy
+
+1. Ensure the Docker daemon is running on your machine, then preview and deploy the program with `pulumi up`. The program deploys 24 resources and takes about 10 minutes to complete.
+
+1. View the stack output properties via `pulumi stack output`. The stack output property `frontendUrl` is the URL and port of the deployed app:
+
+ ```bash
+ $ pulumi stack output frontendURL
+ ***.elb.us-west-2.amazonaws.com
+ ```
+
+1. In a browser, navigate to the URL for `frontendURL`. You should see the voting app webpage.
+
+ ![Voting app screenshot](./voting-app-webpage.png)
+
+### Delete resources
+
+When you're done, run `pulumi destroy` to delete the program's resources:
+
+```
+$ pulumi destroy
+This will permanently destroy all resources in the 'testing' stack!
+Please confirm that this is what you'd like to do by typing ("testing"): testing
+```
+
+## About the code
+
+At the start of the program, the following lines retrieve the value for the Redis password by reading a [configuration value](https://pulumi.io/reference/config.html). This is the same value that was set above with the command `pulumi config set redisPassword `:
+
+```typescript
+let config = new pulumi.Config();
+let redisPassword = config.require("redisPassword");
+```
+
+In the program, the value can be used like any other variable.
+
+### Resources
+
+The program provisions two top-level resources with the following commands:
+
+```typescript
+let redisCache = new awsx.ecs.FargateService("voting-app-cache", ... )
+let frontend = new awsx.ecs.FargateService("voting-app-frontend", ... )
+```
+
+The definition of `redisCache` uses the `image` property of `FargateService.taskDefinitionArgs` to point to an existing Docker image. In this case, this is the image `redis` at tag `alpine` on Docker Hub. The `redisPassword` variable is passed to the startup command for this image.
+
+The definition of `frontend` is more interesting, as it uses `image` property of `FargateService.taskDefinitionArgs` to point to a folder with a Dockerfile, which in this case is a Python Flask app. Pulumi automatically invokes `docker build` for you and pushes the container to ECR.
+
+So that the `frontend` container can connect to `redisCache`, the environment variables `REDIS`, `REDIS_PORT` are defined. Using the `redisListenre.endpoint` property, it's easy to declare the connection between the two containers.
+
+The Flask app uses these environment variables to connect to the Redis cache container. See the following in [`frontend/app/main.py`](frontend/app/main.py):
+
+```python
+redis_server = os.environ['REDIS']
+redis_port = os.environ['REDIS_PORT']
+redis_password = os.environ['REDIS_PWD']
+```
diff --git a/aws-ts-voting-app/frontend/Dockerfile b/aws-ts-voting-app/frontend/Dockerfile
new file mode 100755
index 000000000..690a4775b
--- /dev/null
+++ b/aws-ts-voting-app/frontend/Dockerfile
@@ -0,0 +1,3 @@
+FROM tiangolo/uwsgi-nginx-flask:python3.6
+RUN pip install redis
+COPY /app /app
diff --git a/aws-ts-voting-app/frontend/LICENSE b/aws-ts-voting-app/frontend/LICENSE
new file mode 100644
index 000000000..d1ca00f20
--- /dev/null
+++ b/aws-ts-voting-app/frontend/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation. All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
\ No newline at end of file
diff --git a/aws-ts-voting-app/frontend/app/config_file.cfg b/aws-ts-voting-app/frontend/app/config_file.cfg
new file mode 100755
index 000000000..23c142d8f
--- /dev/null
+++ b/aws-ts-voting-app/frontend/app/config_file.cfg
@@ -0,0 +1,5 @@
+# UI Configurations
+TITLE = 'Pulumi Voting App'
+VOTE1VALUE = 'Tabs'
+VOTE2VALUE = 'Spaces'
+SHOWHOST = 'false'
\ No newline at end of file
diff --git a/aws-ts-voting-app/frontend/app/main.py b/aws-ts-voting-app/frontend/app/main.py
new file mode 100755
index 000000000..d1fe50f42
--- /dev/null
+++ b/aws-ts-voting-app/frontend/app/main.py
@@ -0,0 +1,71 @@
+# Copied from https://github.com/Azure-Samples/azure-voting-app-redis
+
+from flask import Flask, request, render_template
+import os
+import random
+import redis
+import socket
+import sys
+
+app = Flask(__name__)
+
+# Load configurations
+app.config.from_pyfile('config_file.cfg')
+button1 = app.config['VOTE1VALUE']
+button2 = app.config['VOTE2VALUE']
+title = app.config['TITLE']
+
+# Redis configurations
+redis_server = os.environ['REDIS']
+redis_port = os.environ['REDIS_PORT']
+redis_password = os.environ['REDIS_PWD']
+
+# Redis Connection
+try:
+ r = redis.StrictRedis(host=redis_server, port=redis_port, password=redis_password)
+ r.ping()
+except redis.ConnectionError:
+ exit('Failed to connect to Redis, terminating.')
+
+# Init Redis
+if not r.get(button1): r.set(button1,0)
+if not r.get(button2): r.set(button2,0)
+
+@app.route('/', methods=['GET', 'POST'])
+def index():
+
+ if request.method == 'GET':
+
+ # Get current values
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+
+ # Return index with values
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+ elif request.method == 'POST':
+
+ if request.form['vote'] == 'reset':
+
+ # Empty table and return results
+ r.set(button1,0)
+ r.set(button2,0)
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+ else:
+
+ # Insert vote result into DB
+ vote = request.form['vote']
+ r.incr(vote,1)
+
+ # Get current values
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+
+ # Return results
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+if __name__ == "__main__":
+ app.run()
diff --git a/aws-ts-voting-app/frontend/app/static/default.css b/aws-ts-voting-app/frontend/app/static/default.css
new file mode 100755
index 000000000..cb2cb0ada
--- /dev/null
+++ b/aws-ts-voting-app/frontend/app/static/default.css
@@ -0,0 +1,96 @@
+body {
+ background-color:#F8F8F8;
+}
+
+div#container {
+ margin-top:5%;
+}
+
+div#space {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ height: 10px;
+
+}
+
+div#logo {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+ /*border-bottom: 1px solid black;*/
+}
+
+div#form {
+ padding: 20px;
+ padding-right: 20px;
+ padding-top: 20px;
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+ border-bottom: 1px solid black;
+ border-top: 1px solid black;
+}
+
+div#results {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+}
+
+.button {
+ background-color: #4CAF50; /* Green */
+ border: none;
+ color: white;
+ padding: 16px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 4px 2px;
+ -webkit-transition-duration: 0.4s; /* Safari */
+ transition-duration: 0.4s;
+ cursor: pointer;
+ width: 250px;
+}
+
+.button1 {
+ background-color: white;
+ color: black;
+ border: 2px solid #008CBA;
+}
+
+.button1:hover {
+ background-color: #008CBA;
+ color: white;
+}
+.button2 {
+ background-color: white;
+ color: black;
+ border: 2px solid #555555;
+}
+
+.button2:hover {
+ background-color: #555555;
+ color: white;
+}
+
+.button3 {
+ background-color: white;
+ color: black;
+ border: 2px solid #f44336;
+}
+
+.button3:hover {
+ background-color: #f44336;
+ color: white;
+}
\ No newline at end of file
diff --git a/aws-ts-voting-app/frontend/app/templates/index.html b/aws-ts-voting-app/frontend/app/templates/index.html
new file mode 100755
index 000000000..c92e65976
--- /dev/null
+++ b/aws-ts-voting-app/frontend/app/templates/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/aws-ts-voting-app/index.ts b/aws-ts-voting-app/index.ts
new file mode 100644
index 000000000..a363fcb6e
--- /dev/null
+++ b/aws-ts-voting-app/index.ts
@@ -0,0 +1,51 @@
+// Copyright 2017, Pulumi Corporation. All rights reserved.
+
+import * as pulumi from "@pulumi/pulumi";
+import * as awsx from "@pulumi/aws-infra";
+
+// Get the password to use for Redis from config.
+let config = new pulumi.Config();
+let redisPassword = config.require("redisPassword");
+let redisPort = 6379;
+
+// The data layer for the application
+// Use the 'image' property to point to a pre-built Docker image.
+let redisListener = new awsx.elasticloadbalancingv2.NetworkListener("voting-app-cache", { port: redisPort });
+let redisCache = new awsx.ecs.FargateService("voting-app-cache", {
+ taskDefinitionArgs: {
+ containers: {
+ redis: {
+ image: "redis:alpine",
+ memory: 512,
+ portMappings: [redisListener],
+ command: ["redis-server", "--requirepass", redisPassword],
+ },
+ },
+ },
+});
+
+let redisEndpoint = redisListener.endpoint();
+
+// A custom container for the frontend, which is a Python Flask app
+// Use the 'build' property to specify a folder that contains a Dockerfile.
+// Pulumi builds the container for you and pushes to an ECR registry
+let frontendListener = new awsx.elasticloadbalancingv2.NetworkListener("voting-app-frontend", { port: 80 });
+let frontend = new awsx.ecs.FargateService("voting-app-frontend", {
+ taskDefinitionArgs: {
+ containers: {
+ votingAppFrontend: {
+ image: awsx.ecs.Image.fromPath("./frontend"), // path to the folder containing the Dockerfile
+ memory: 512,
+ portMappings: [frontendListener],
+ environment: redisEndpoint.apply(e => [
+ { name: "REDIS", value: e.hostname },
+ { name: "REDIS_PORT", value: e.port.toString() },
+ { name: "REDIS_PWD", value: redisPassword },
+ ]),
+ },
+ },
+ },
+});
+
+// Export a variable that will be displayed during 'pulumi up'
+export let frontendURL = frontendListener.endpoint();
diff --git a/aws-ts-voting-app/package.json b/aws-ts-voting-app/package.json
new file mode 100644
index 000000000..56ab14618
--- /dev/null
+++ b/aws-ts-voting-app/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "voting-app",
+ "version": "0.1.0",
+ "devDependencies": {
+ "@types/node": "^8.0.27"
+ },
+ "dependencies": {
+ "@pulumi/pulumi": "dev",
+ "@pulumi/aws": "dev",
+ "@pulumi/aws-infra": "dev",
+ "@pulumi/cloud-aws": "dev"
+ }
+}
diff --git a/aws-ts-voting-app/tsconfig.json b/aws-ts-voting-app/tsconfig.json
new file mode 100644
index 000000000..ae4e90d4b
--- /dev/null
+++ b/aws-ts-voting-app/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "outDir": "bin",
+ "target": "es6",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "experimentalDecorators": true,
+ "pretty": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictNullChecks": true
+ },
+ "files": [
+ "index.ts"
+ ]
+}
diff --git a/aws-ts-voting-app/voting-app-webpage.png b/aws-ts-voting-app/voting-app-webpage.png
new file mode 100644
index 000000000..b612eba0d
Binary files /dev/null and b/aws-ts-voting-app/voting-app-webpage.png differ