Create a Hugo static website behind a Digital Ocean droplet
TypeScriptCertainly!
This Pulumi program will accomplish your goal by doing the following:
- Creates a DigitalOcean Droplet running Ubuntu
- Uses user data (a startup shell script) to install Hugo, generate a new site, and start the Hugo server on the Droplet.
import * as pulumi from "@pulumi/pulumi"; import * as digitalocean from "@pulumi/digitalocean"; // Define the size (hardware specs) and region of the droplet let size = "s-1vcpu-1gb"; // adjust as needed let region = "nyc3"; // adjust as needed // Create a droplet resource let droplet = new digitalocean.Droplet("myDroplet", { image: "ubuntu-18-04-x64", region: region, size: size, sshKeys: ["YOUR_SSH_PUBLIC_KEY_ID_OR_FINGERPRINT"], userData: `#!/bin/bash sudo apt-get -y update sudo apt-get -y install hugo cd /root hugo new site mysite cd mysite echo 'Hello, World' > content/_index.md nohup hugo server -D --bind=0.0.0.0 --baseUrl=http://\$\(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address\) & `, }); // Export the public IP address of the Droplet export const publicIp = droplet.ipv4Address; // Export the public hostname of the Droplet export const publicDns = droplet.ipv4Address;
Please replace
"YOUR_SSH_PUBLIC_KEY_ID_OR_FINGERPRINT"
with your actual SSH public key ID or fingerprint.Be sure to replace
["YOUR_SSH_PUBLIC_KEY_ID_OR_FINGERPRINT"]
with an array of the SSH IDs or fingerprints you want to use for this Droplet. You can retrieve this information using the DigitalOcean API or from the DigitalOcean control panel.This setup will give you a Hugo server serving traffic on the public IP of the droplet. Note: This is a basic setup without a domain name, HTTPS or a proper web server like Nginx in front of the Hugo server. Depending on your requirements, you might want to enhance this setup.
Refer to the official Pulumi documentation for more information about the digitalocean.Droplet class.
Create a Hugo static website behind a Digital Ocean droplet. I want to update the site from a local computer via scp.
TypeScriptTo accomplish your goal, we'll keep using the DigitalOcean Droplet with the shell script for setting up Hugo as you have it now. Additionally, we'll add another script at the end of the user data that will set up
scp
for file transfers.Here's the updated Pulumi program:
import * as pulumi from "@pulumi/pulumi"; import * as digitalocean from "@pulumi/digitalocean"; // Define the size (hardware specs) and region of the droplet let size = "s-1vcpu-1gb"; // adjust as needed let region = "nyc3"; // adjust as needed // Create a droplet resource let droplet = new digitalocean.Droplet("myDroplet", { image: "ubuntu-18-04-x64", region: region, size: size, sshKeys: ["YOUR_SSH_PUBLIC_KEY_ID_OR_FINGERPRINT"], userData: `#!/bin/bash sudo apt-get -y update sudo apt-get -y install hugo openssh-server cd /root hugo new site mysite cd mysite echo 'Hello, World' > content/_index.md nohup hugo server -D --bind=0.0.0.0 --baseUrl=http://\$\(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address\) & `, }); // Export the public IP address of the Droplet export const publicIp = droplet.ipv4Address; // Export the public hostname of the Droplet export const publicDns = droplet.ipv4Address;
This creates a DigitalOcean Droplet with Hugo and an OpenSSH server installed. The Hugo serves content from
/root/mysite
, and scp should be able to transfer files to this directory for updates.Once the Pulumi program is executed and the droplet is up and running, you can use the exported IP address (
pulumi stack output publicIp
) to connect to the droplet and transfer your Hugo site content. Assuming the content is in a directory named "my-hugo-site" on your local machine, you can do so with the following command:scp -r my-hugo-site root@<droplet-ip>:/root/mysite
Remember to replace
<droplet-ip>
with the actual IP of the droplet.The
-r
option in thescp
command indicates that the operation is recursive, meaning it will copy the directory and all of its contents.If you're not prompted for password when running
scp
, that means you've set up passwordless sign-in correctly with your SSH keys.Create a Flask Python web server on and AWS public on three availability zones behind a load balancer. Create a Postgresql database on a private network. An AWS bastion should protect both networks.
PythonSure, I will guide you on how to accomplish the realization of your architecture using Pulumi.
Please note that exact implementation can differ based on your specific use case and this is a simplified example. In the actual scenario, there might be need for additional resources, like IAM roles or security groups rules, not included here.
First, you import the necessary Pulumi and AWS packages.
import pulumi from pulumi_aws import ec2, elb, rds
Then create a VPC and assign it a CIDR block:
vpc = ec2.Vpc("app-vpc", cidr_block="172.31.0.0/16", tags={ "Name": "app-vpc", }, )
We create both a public subnet for the ELB and bastion instance and a private subnet for the RDS instance:
public_subnet = ec2.Subnet("app-public-subnet", vpc_id=vpc.id, cidr_block="172.31.32.0/20", availability_zone="us-west-2a", tags={ "Name": "app-public-subnet", }, ) private_subnet = ec2.Subnet("app-private-subnet", vpc_id=vpc.id, cidr_block="172.31.0.0/20", availability_zone="us-west-2b", tags={ "Name": "app-private-subnet", }, )
Here, the availability zone is just hardcoded to us-west-2 and us-west-2b for simplicity. In a real-world scenario, you would probably want to query the available zones.
Next, let's set up the ELB:
app_lb = elb.LoadBalancer("app-lb", subnets=[public_subnet.id], security_groups=[app_security_group.id], ) app_lb_health_check = elb.LoadBalancerHealthCheck("app-lb-hc", load_balancer_name=app_lb.name, target="HTTP:5000/login", interval=30, healthy_threshold=2, unhealthy_threshold=2, timeout=3, )
Let's set up bastion host for secure administration purposes:
bastion = ec2.Instance("app-bastion", ami="ami-0c94855ba95c574c8", # An Amazon linux AMI instance_type="t2.micro", subnet_id=public_subnet.id, key_name="your-ssh-key-name", security_groups=[bastion_security_group.id], user_data="""#!/bin/bash sudo yum -y update sudo yum -y install python3 sudo pip3 install boto3 """, ) pulumi.export("bastion_ip", bastion.public_ip)
Finally, we create our PostgreSQL RDS instance inside the private subnet. Please note, you should save the master password in a secure way:
pg_user = "pg_user" pg_pass = "Passw0rd" pg_db = "myapp" database = rds.Instance("app-db", engine="postgres", engine_version="9.6.3", instance_class="db.t2.micro", name=pg_db, username=pg_user, password=pg_pass, allocated_storage=10, multi_az=False, storage_type="gp2", vpc_security_group_ids=[app_security_group.id], db_subnet_group_name=db_subnet_group.name, publicly_accessible=False, ) pulumi.export("db_address", database.address) pulumi.export("db_port", database.port)
Please note that your Pulumi proposed program is missing definitions for security groups required for load balancer, the bastion host, and the database, which must be set based on your specific security needs. For the bastion host, Northern Virginia's AMI ID:
ami-0c94855ba95c574c8
is used, but it must be selected and replaced according to your AWS Region.This Pulumi Python program is based on the aws.lb.LoadBalancer and aws.rds.Instance resources.
Modify the above request to use the 1Password CLI to set credentials.
PythonSure thing! The 1Password CLI does not have a dedicated Pulumi resource or package, but I can demonstrate how to integrate it in your Python Pulumi program by executing the 1Password CLI command using Python's built-in
subprocess
package.Keep in mind that Pulumi itself doesn't handle secrets by running external programs. This solution assumes that
op
(the 1Password CLI) is installed and you're signed in, as these steps are outside the Pulumi and Python's scope.Here is the updated version of your script. This script retrieves the username and password from a 1Password vault and then uses them to create the RDS instance:
import pulumi from pulumi_aws import rds import subprocess import json # Execute 'op' command to fetch item details op_result = subprocess.run(['op', 'get', 'item', 'DatabaseCredentials'], stdout=subprocess.PIPE) op_result_str = op_result.stdout.decode('utf-8') op_result_json = json.loads(op_result_str) # Extract username and password pg_user = op_result_json['details']['fields'][0]['value'] pg_pass = op_result_json['details']['fields'][1]['value'] pg_db = "myapp" # Create RDS instance database = rds.Instance("app-db", engine="postgres", engine_version="9.6.3", instance_class="db.t2.micro", name=pg_db, username=pg_user, password=pg_pass, allocated_storage=10, multi_az=False, storage_type="gp2", vpc_security_group_ids=[app_security_group.id], db_subnet_group_name=db_subnet_group.name, publicly_accessible=False, ) pulumi.export("db_address", database.address) pulumi.export("db_port", database.port)
This script assumes that you have an item in 1Password called 'DatabaseCredentials'. The 'DatabaseCredentials' item should have the database user and password stored in its fields. You may have to adjust the field indices (
['fields'][0]['value']
and['fields'][1]['value']
) based on your 1Password item's structure.The
op
command needs to know which 1Password account and vault to access. To sign in to your account, use theop signin <account_shorthand>
command, for exampleop signin myaccount
.Please replace
'DatabaseCredentials'
with the correct name of your item, and runop signin
before executing the Pulumi script.Please note, this code runs in a specific context where the 1Password CLI command is accessible, and the Pulumi Python SDK is installed. Your context may be a Dockerfile, a local computer, a CI/CD server, a Pulumi webhook, or something else. Check to ensure this context meets the prerequisites to run both Pulumi and the 1Password CLI binary successfully.