Deploying to the cloud

On this page 9

buddy deploy builds the application and ships it. Where it ships is decided by one setting, cloud.provider in config/cloud.ts.

The three targets

cloud.providerTargetHow it gets there
'aws' (default)AWSGenerates the infrastructure and creates or updates a CloudFormation stack
'hetzner'A Hetzner Cloud serverProvisions the server through the Hetzner API, then deploys over SSH
'ssh'A Linux host you already ownAdopts and bootstraps the host over SSH, then deploys to it

CLOUD_PROVIDER in the environment overrides the config value. With neither set, the provider is aws.

// config/cloud.ts
cloud: {
  provider: 'hetzner',
},

The Hetzner and ssh targets share one pipeline: a release tarball copied over SSH, systemd units per site, and the rpx gateway in front. They differ in whether a server is created for you.

Preview any target before it changes anything:

buddy deploy --dry-run

AWS (provider: 'aws')

Prerequisites

buddy configure:aws
buddy key:generate
buddy env:set APP_URL https://example.com
buddy env:set APP_ENV production

Review config/cloud.ts, config/team.ts, and the hooks in cloud/deploy-script.ts before the first deployment.

Preview and deploy

buddy cloud --diff
buddy deploy

Server mode uses EC2 and an Application Load Balancer. Serverless mode uses Lambda, API Gateway, CloudFront, and S3. Select the mode in config/cloud.ts.

CloudFormation deployments include IAM capabilities and roll back failed stack creation. Stacks tags resources with the environment, project, and framework ownership metadata.

Operations

buddy cloud --ssh
buddy cloud --invalidate-cache
buddy cloud:add --jump-box

buddy cloud:remove and buddy cloud:cleanup are destructive and require confirmation unless --yes is supplied intentionally.

Hetzner Cloud (provider: 'hetzner')

Set provider: 'hetzner' and supply a Hetzner API token, either as hetzner.apiToken in config/cloud.ts or as HCLOUD_TOKEN in the environment.

The deploy registers your local SSH public key on the server, so ~/.ssh/id_ed25519.pub must exist. Generate one with ssh-keygen -t ed25519 if it does not.

buddy deploy --prod

The first deploy creates the server, firewall, SSH key and any managed services. Later deploys reuse them. DNS records for every site that declares a domain are reconciled against the box address, and certificates are issued automatically.

Several projects can share one Hetzner box. The owning project deploys normally, and each other project sets cloud.attachTo to the owner's project.slug. An attached project skips provisioning and deploys only its own sites.

A host you own (provider: 'ssh')

Deploy to a Linux box that already exists: a Raspberry Pi on your desk, a rented dedicated server, a VM someone else provisioned. Nothing is created through a provider API. The host is checked and bootstrapped in place, then deployed to exactly like the Hetzner box.

cloud: {
  provider: 'ssh',
},

ssh: {
  profile: 'raspberry-pi',
  hosts: [{ host: 'pi-stacks.local', user: 'pi' }],
  lan: { tls: 'local-ca' },
},

Host, user, port and key can also come from TS_CLOUD_SSH_HOST, TS_CLOUD_SSH_USER, TS_CLOUD_SSH_PORT and TS_CLOUD_SSH_KEY. The environment wins over the config.

The one behaviour that differs from Hetzner is what happens on a private address. When the host sits on a LAN, the deploy publishes no DNS, requests no Let's Encrypt certificate, skips the CDN and skips mail reconciliation, and prints the LAN URLs instead. Publishing an A record for 192.168.1.42 would point every visitor's browser at whatever occupies that address on their own network, and an ACME challenge cannot reach a host the internet cannot route to.

Give the host a routable address, forward ports 80 and 443 to it, declare a domain on a site, and set ssh.publicIp. DNS and certificates then work exactly as they do for Hetzner.

The full walkthrough, including flashing an image, first-boot configuration, trusting the box's local certificate authority, and troubleshooting, is in Deploying to a Raspberry Pi.

Deployment hooks

export default {
  beforeDeploy({ environment, region }) {
    console.log(`Deploying ${environment} in ${region}`)
  },

  afterDeploy({ outputs }) {
    console.log(outputs.Endpoint)
  },
}

Use hooks for deterministic preflight checks and post-deploy smoke tests. Keep credentials in the environment, never in the hook source. Hooks run in the deployment process on your machine, not on the target server.

Rolling back

buddy deploy:rollback

Activates a preserved release. --env <environment> selects the environment, --to <release> names a specific preserved release, and --dry-run previews the change without making it. On the AWS path, buddy cloud:remove tears the stack down instead.