How this site actually runs
This page is hosted by the system it describes. A look under the hood of jimpapadimas.com — the AWS infrastructure, the container setup, the delivery model, and the security decision behind each piece.
The system at a glance
Everything runs on one small EC2 instance in eu-north-1 with a static Elastic IP. A single host nginx terminates TLS and routes each hostname to its own Docker container — one container per site. New projects arrive as subdomains on the same box; no new servers, no new IPs.
There is no public SSH. Operator access and deploys both go through AWS Systems Manager, so port 22 stays closed to the internet entirely. The design goal throughout: spin up a new project freely, while keeping the attack surface and the running cost small.
Request path
DNS points the domain and each subdomain at one Elastic IP — a static address that survives instance restarts. nginx then does the interesting work: it terminates TLS and uses name-based virtual hosting to match the incoming Host header against a per-site server block, forwarding each hostname to the right container.
Every container binds only to 127.0.0.1, so nothing is reachable from the internet except through nginx. Adding a project is three repeatable steps — a DNS record, an nginx server block on a fresh local port, and a Let’s Encrypt certificate — and the architecture above doesn’t change.
From commit to live
A push to main triggers a pipeline that authenticates to AWS via OIDC federation — no cloud keys stored in the repository. It builds the image, scans it with Trivy and fails the build on high-severity findings, then pushes a commit-SHA-tagged image to ECR. Deployment is triggered over SSM: the instance pulls the new image, swaps the container, and runs a health check that rolls back automatically if the new release doesn’t answer.
Status: this is the target delivery model. While the pipeline is being finalised, deploys are done by rebuilding the image on the box — same end state, manual trigger.
Deliberate choices
→No public SSH
Operator access runs through SSM Session Manager. Port 22 is closed to the internet, and every session is recorded in CloudTrail.
→No static cloud keys
CI authenticates with short-lived OIDC tokens; the local CLI uses temporary aws login credentials. Nothing long-lived sits on disk or in the repo.
→Least-privilege IAM
The deploy role can push to exactly one ECR repository and send commands to exactly one instance — nothing wider.
→Root as break-glass
The root account has MFA and is otherwise untouched; day-to-day work runs as a separate non-root admin identity.
→Containers on localhost
Application containers bind to 127.0.0.1 only. The host nginx is the single, deliberate ingress point.
→Scanning before ship
Trivy gates the image in CI; ECR also scans on push. A vulnerable image fails the build rather than reaching production.
→TLS by default
Let’s Encrypt certificates per hostname, auto-renewing, with HTTP redirected to HTTPS.
→Immutable releases
Images are tagged by commit SHA, so any deploy is reproducible and a rollback is a single re-point to the previous tag.
Where it’s going
- subdomainsA repeatable project pattern. Record → nginx block on a new port → certificate. Each new build is a subdomain on the same box.
- homebrxA stateful app with auth. Its own compose stack — application plus a Postgres database on a private network and a persistent volume, with scheduled backups.
- wildcard-tlsOne certificate for all subdomains. Moving DNS to Route 53 enables DNS-01 wildcard issuance and drops the per-subdomain certificate step.
- observabilityEyes on the box. Centralised logs, basic metrics, and alerting so failures surface before a visitor finds them.