Skip to main content

Deploy a Web Server to AWS EC2

20 min

In this tutorial, you will deploy a simple web server to an Amazon EC2 instance using Pulumi. You will start by provisioning an instance and a security group, then preview and deploy your changes with a single command.

You will then update the running program to open port 80 and add a startup script, and re-run pulumi up to see how Pulumi computes the minimally disruptive change—updating some resources in place while replacing others.

What you'll learn
  • How to create a new Pulumi project from an AWS template
  • How to define an EC2 instance and a security group
  • How to preview and deploy your infrastructure with pulumi up
  • How to update a running program and understand in-place updates vs. replacements
  • How to export and use stack outputs
Prerequisites

In this tutorial, we will show you how to deploy a simple web server using an Amazon EC2 instance.

Deploy the app#

Step 1: Create a new project from a template#

Create a project directory, webserver, and change into it. Run pulumi new aws-<language> --name myproject to create a new project using the AWS template for your chosen language. Replace myproject with your desired project name.

Terminal window
mkdir webserver && cd webserver
pulumi new aws-typescript --name myproject

Step 2: Create an EC2 instance with HTTP access#

Open your main program file (index.ts, __main__.py, or Program.cs) and replace the contents with the following:

import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
const size = "t2.micro"; // t2.micro is available in the AWS free tier
const ami = aws.ec2.getAmiOutput({
filters: [{
name: "name",
values: ["amzn2-ami-hvm-*"],
}],
owners: ["137112412989"], // This owner ID is Amazon
mostRecent: true,
});
const group = new aws.ec2.SecurityGroup("webserver-secgrp", {
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
],
});
const server = new aws.ec2.Instance("webserver-www", {
instanceType: size,
vpcSecurityGroupIds: [ group.id ], // reference the security group resource above
ami: ami.id,
});
export const publicIp = server.publicIp;
export const publicHostName = server.publicDns;

This example uses the ec2 module of the aws package to create two resources:

AWS ResourceDescriptionResource
Security GroupCreated for allowing incoming HTTP accessaws.ec2.SecurityGroup
EC2 InstanceCreated in that security group using the appropriate Amazon Machine Image (AMI) for the region where you deploy the programaws.ec2.Instance

Step 3: Preview and deploy your resources#

To preview your Pulumi program, run pulumi up. The command shows a preview of the resources that will be created and prompts you to proceed with the deployment. Note that the stack itself is counted as a resource, though it does not correspond to a physical cloud resource.

Terminal window
Previewing update (webserver-dev):
Type Name Plan
+ pulumi:pulumi:Stack myproject-webserver-dev create
+ ├─ aws:ec2:SecurityGroup webserver-secgrp create
+ └─ aws:ec2:Instance webserver-www create
Resources:
+ 3 to create
Do you want to perform this update?
yes
> no
details

Next, proceed with the deployment, which takes about 40 seconds to complete.

Terminal window
Do you want to perform this update? yes
Updating (webserver-dev):
Type Name Status
+ pulumi:pulumi:Stack myproject-webserver-dev created
+ ├─ aws:ec2:SecurityGroup webserver-secgrp created
+ └─ aws:ec2:Instance webserver-www created
Outputs:
publicHostName: "ec2-34-217-110-29.us-west-2.compute.amazonaws.com"
publicIp : "34.217.110.29"
Resources:
+ 3 created
Duration: 40s
Permalink: https://app.pulumi.com/bermudezmt/myproject/webserver-dev/updates/1

Step 4: View your stack resources#

Pulumi Cloud#

To see the full details of the deployment and the resources that are now part of the stack, open the update link in a browser. The Resources tab in the Pulumi Cloud has a link to the AWS console for the provisioned EC2 instance.

Pulumi CLI#

To view the provisioned resources on the command line, run pulumi stack. You’ll also see two stack outputs corresponding to the IP and the fully qualified domain name (FQDN) of the EC2 instance we’ve created.

Current stack is webserver-dev:
Owner: <your-org-name>
Last updated: 10 minutes ago (2019-09-20 11:57:55.90881794 -0700 PDT)
Pulumi version: v1.1.0
Current stack resources (4):
TYPE NAME
pulumi:pulumi:Stack myproject-webserver-dev
pulumi:providers:aws default_1_2_1
aws:ec2/securityGroup:SecurityGroup webserver-secgrp
aws:ec2/instance:Instance webserver-www
More information at: https://app.pulumi.com/<your-org-name>/myproject/webserver-dev
Use `pulumi stack select` to change stack; `pulumi stack ls` lists known ones

Step 5: Update the Pulumi program#

Now that you have an instance of the Pulumi program deployed, you may want to make changes. You do so by updating the Pulumi program to define the new state you want your infrastructure to be in, and then running pulumi up to commit the changes.

Replace the creation of the two resources with the following code. This exposes an additional port, 80, and adds a startup script to run a simple HTTP server at startup.

...
const group = new aws.ec2.SecurityGroup("webserver-secgrp", {
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
// ^-- ADD THIS LINE
],
});
const userData = // <-- ADD THIS DEFINITION
`#!/bin/bash
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &`;
const server = new aws.ec2.Instance("webserver-www", {
instanceType: size,
vpcSecurityGroupIds: [ group.id ], // reference the security group resource above
ami: ami.id,
userData: userData, // <-- ADD THIS LINE
});
...

Run pulumi up to preview and deploy the changes. You’ll see two changes: the ingress property of the SecurityGroup will be updated in-place. Secondly, the Instance will be replaced with a new EC2 instance which will run the new script on startup. Pulumi understands which changes to a given cloud resource can be made in place, which require replacement, and computes the minimally disruptive change to achieve the desired state.

Terminal window
Previewing update (webserver-dev):
Type Name Plan Info
pulumi:pulumi:Stack myproject-webserver-dev
~ ├─ aws:ec2:SecurityGroup webserver-secgrp update [diff: ~ingress]
+- └─ aws:ec2:Instance webserver-www replace [diff: +userData~securityGroups]
Resources:
~ 1 to update
+-1 to replace
2 changes. 1 unchanged

When prompted to confirm your update, you may review the planned changes to your stack resources by selecting details.

Terminal window
Do you want to perform this update? details
pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:webserver-dev::myproject::pulumi:pulumi:Stack::myproject-webserver-dev]
~ aws:ec2/securityGroup:SecurityGroup: (update)
[id=sg-0317c16c7015d7fd0]
[urn=urn:pulumi:webserver-dev::myproject::aws:ec2/securityGroup:SecurityGroup::webserver-secgrp]
[provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
~ ingress: [
~ [0]: {
~ cidrBlocks : [
~ [0]: "0.0.0.0/0" => "0.0.0.0/0"
]
- description: ""
~ fromPort : 22 => 22
~ protocol : "tcp" => "tcp"
~ self : false => false
~ toPort : 22 => 22
}
+ [1]: {
+ cidrBlocks: [
+ [0]: "0.0.0.0/0"
]
+ fromPort : 80
+ protocol : "tcp"
+ self : false
+ toPort : 80
}
]
++aws:ec2/instance:Instance: (create-replacement)
[id=i-0a639b62c37bf712c]
[urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
[provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
~ securityGroups: [
~ [0]: "webserver-secgrp-2398ba7" => output<string>
]
+ userData : "#!/bin/bash\necho \"Hello, World!\" > index.html\nnohup python -m SimpleHTTPServer 80 &"
+-aws:ec2/instance:Instance: (replace)
[id=i-0a639b62c37bf712c]
[urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
[provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
~ securityGroups: [
~ [0]: "webserver-secgrp-2398ba7" => output<string>
]
+ userData : "#!/bin/bash\necho \"Hello, World!\" > index.html\nnohup python -m SimpleHTTPServer 80 &"
--aws:ec2/instance:Instance: (delete-replaced)
[id=i-0a639b62c37bf712c]
[urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
[provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]

Select yes to confirm the update.

You can use pulumi stack output to get the value of stack outputs from the CLI. To do so, curl the EC2 instance to confirm that the HTTP server is running. Stack outputs can also be viewed in the Pulumi Cloud.

Terminal window
curl $(pulumi stack output publicHostName)
Hello, World!

Clean up#

Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.

  1. Run pulumi destroy to tear down all resources. You’ll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete.
  2. To delete the stack itself, run pulumi stack rm. Note that this command deletes all deployment history from the Pulumi Service.

Summary#

In this tutorial, we showed you how to use Pulumi programs to create and manage cloud resources in AWS, using TypeScript, Python, or C#.

Next steps#

Related

The infrastructure as code platform for any cloud.