Skip to content

deploy: run ftwcloudd at cloud.ftw.energy in Stockholm - #2

Merged
frahlg merged 2 commits into
mainfrom
claude/cloud-aws-deploy-ed40ed
Oct 13, 2026
Merged

frahlg merged 2 commits into
mainfrom
claude/cloud-aws-deploy-ed40ed

Conversation

@frahlg

@frahlg frahlg commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

ftwcloudd now runs at https://cloud.ftw.energy on one t4g.small in eu-north-1 (Stockholm). Fredrik asked for this on 13 Oct 2026. No box sends to it yet. That needs a Core built from srcfl/ftw#1595 and a separate decision by the owner.

This PR records the running host. deploy/bootstrap.sh builds the host, and deploy/README.md holds the ids, how to update, check and tear it down, and the cost.

What runs

What Value
Account, region 976075258475 (shared for now), eu-north-1a, default VPC
Instance i-0ecac4cbc6b5f8adc, t4g.small, AL2023 arm64, IMDSv2 required, no key pair
Disk vol-0760cad0e8551e5e4, 20 GB gp3, encrypted
Elastic IP 13.50.84.149 (eipalloc-0085f3e68b6f36da4)
Security group sg-098992ad382ecf94a: 443/tcp in, nothing else
Role ftw-cloud-host: AmazonSSMManagedInstanceCore only
DNS cloud.ftw.energy A, grey cloud
Running main at d8d3275, image ftw-cloud:d8d32752ff2d

Every resource carries the tag Project=ftw-cloud. The cost is about $18 a month: $12.56 for the instance, $1.67 for the disk and $3.65 for the public IPv4 address.

Where this differs from the ADR and the old deploy notes

  • The service key lives on the host, not in Secrets Manager. The role may only talk to SSM, so it cannot read a secret. ftwcloudd keygen made the key on the host. It rests on the encrypted disk, readable by root only, and is copied to tmpfs before Docker starts. Move it to Secrets Manager before a Core release carries its public half.
  • The host builds its own image. compose.yml names an image and has no build:. ftw-cloud-deploy builds the checked-out commit, tags it ftw-cloud:<12 hex> and writes that tag to /etc/ftw-cloud/compose.env. Images built in CI and deployed by digest come later.
  • Outbound traffic is open, because the host needs Docker Hub, gcr.io, the Go module proxy and GitHub to build.
  • SSM session logs to a separate account are not set up.

Checked from outside

$ curl -sS -D - https://cloud.ftw.energy/healthz
HTTP/1.1 200 OK
Cache-Control: no-store
Link: </source>; rel="copyright"; title="AGPL-3.0 source"
ok

$ curl -sS -D - -o src.tar.gz https://cloud.ftw.energy/source
HTTP/1.1 200 OK
Content-Disposition: attachment; filename="ftw-cloud-source.tar.gz"
Content-Length: 41321
Content-Type: application/gzip
(39 entries, including ./LICENSE, ./go.mod, ./cmd/ftwcloudd/main.go)

$ openssl s_client -connect cloud.ftw.energy:443 -servername cloud.ftw.energy | openssl x509 -noout -subject -issuer -dates
subject= /CN=cloud.ftw.energy
issuer= /C=US/O=Let's Encrypt/CN=YE2
notBefore=Oct 13 04:03:55 2026 GMT
notAfter=Jan 11 04:03:54 2027 GMT
Protocol: TLSv1.3, Verify return code: 0 (ok)
TLS 1.2: refused (tlsv1 alert protocol version)

$ nmap -Pn -p- -T5 --max-retries 0 --min-rate 4000 13.50.84.149
Not shown: 65534 filtered tcp ports (no-response)
PORT    STATE SERVICE
443/tcp open  https
Nmap done: 1 IP address (1 host up) scanned in 709.66 seconds

The full scan overlapped the first reboot by about 20 seconds. The security group lets in 443 and nothing else, so the result does not depend on the host.

On the host, through SSM, these all held:

  • ss -ltn shows only 0.0.0.0:443, [::]:443 and one loopback port. Amazon Linux starts sshd, and cloud-init.service wants it at every boot, so bootstrap.sh masks it. Disabling it was not enough.
  • There is no swap and no swap line in fstab, and kernel.core_pattern = |/bin/false.
  • The container runs as nonroot, with a read-only root file system, cap_drop ALL and core size 0 in /proc/<pid>/limits.
  • The service key on disk is root:root 600, and its copy in /run (tmpfs) is 65532 400.
  • The service's public key is BG6EvBxFzSHPVsQOES6PYaKFo5vHQmte9lJ6D5OBIT9JgcBrJAOxAucdqKXxS76pToQ7orI1Ivsno0OtxbMuliQ.

Reboot test

I rebooted the instance twice from outside (aws ec2 reboot-instances):

  • /healthz stopped answering and was back at 200 ok about 20 seconds after each reboot, with no one logging in.
  • ftw-cloud-key.service copied the key to tmpfs, and the container started with restarts=0 and image ftw-cloud:d8d32752ff2d.
  • After the reboot the certificate was the same one, from the ACME cache on the volume: CN=cloud.ftw.energy, Let's Encrypt YE2, valid to 11 Jan 2027, Verify return code: 0 (ok), TLS 1.3.
  • The first reboot brought sshd back on 22, which led to the mask. After the second reboot sshd is inactive, ss -ltn shows only 443 outward, and nc -z 13.50.84.149 22 fails.
  • Swap stays off.

🤖 Generated with Claude Code

bootstrap.sh brings up an Amazon Linux 2023 host through SSM: swap, core
dumps and sshd off, Docker as on the relay host, and ftw-cloud-deploy,
which builds the checked-out commit and starts a new container. The
service key is made on the host and copied to tmpfs before Docker starts.

deploy/README.md records the running host: ids, how to update, check and
tear it down, and the cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T05:34:00.543264Z a79d84e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

for img in $(docker image ls ftw-cloud --format '{{.Repository}}:{{.Tag}}'); do
[ "$img" = "ftw-cloud:$rev" ] || docker image rm "$img"
done

P1 Badge Keep the previous image until the new service is verified

When an update produces an image whose process exits after startup, docker compose up -d can return after launching it because detached mode runs containers in the background; Docker provides --wait separately to wait for a running or healthy state (Docker documentation). Since this Compose service has no health check, the following loop can immediately delete every previous image while the replacement is restarting or unable to serve /healthz, leaving the single production host down without a local rollback image. Verify the endpoint and retain the prior tag until that succeeds before pruning old images.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

A reboot test showed sshd listening on 22 again: cloud-init.service
wants it, so disabling the unit is not enough. Masking it keeps it off
across reboots. The security group already dropped 22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit f344010 into main Oct 13, 2026
1 check passed
frahlg added a commit that referenced this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant