This repository contains the source code and infrastructure for my personal portfolio website.
The stack is:
- Next.js (app UI) under
app/ - Postgres (data)
- Pulumi + Docker (infrastructure-as-code) under
infra/ - Traefik (reverse proxy, routing, TLS)
- Additional projects under
projects/
The site is served behind Traefik with optional automatic TLS (Let’s Encrypt).
app/ # Next.js portfolio app
src/...
infra/ # Pulumi "portfolio-infra" project (Traefik, Postgres, portfolio, observability)
Pulumi.yaml
Pulumi.dev.yaml
Pulumi.prod.yaml
README.md
projects/
hello-world-react/ # Example sub-project
src/
infra/ # Pulumi stack attached to portfolio-infra (via Traefik)
infra-standalone/ # Pulumi stack to run this project alone (no portfolio, no Traefik)
scripts.sh # Legacy Docker Compose helpers (see "Legacy scripts" section)
For more infrastructure details, see infra/README.md.
You can develop the portfolio and projects directly with their dev servers.
cd app
yarn install
yarn devThis starts Next.js on http://localhost:3000.
cd projects/hello-world-react
yarn install
yarn devThis starts Vite on http://localhost:3001 (see projects/hello-world-react/vite.config.ts).
In this mode there is no Traefik, no Postgres, no Pulumi – just app-only development.
The full environment is managed by the Pulumi project in infra/Pulumi.yaml (name: portfolio-infra).
Two stacks are preconfigured:
- dev – HTTP, no TLS,
DOMAIN_NAME=localhost - prod – HTTPS, real
DOMAIN_NAMEand TLS enabled
- Docker
- Pulumi CLI (
pulumi)
From repo root:
cd infra
# Dev stack (HTTP on localhost)
pulumi stack select dev
pulumi up
# Prod stack (HTTPS on rashadataf.com, for example)
pulumi stack select prod
pulumi upThe dev and prod stack configs are stored in:
Core stack provisions:
- Docker network
app-network(seeAPP_NETWORK_NAMEin infra/Pulumi.yaml) - Postgres 17 container with a named volume
- Traefik reverse proxy
- Portfolio app container built from app/Dockerfile
- Observability stack via the
observability:Observationcomponent
Access:
- Dev:
http://localhost(orhttp://localhost:80) - Prod:
https://rashadataf.com(andhttps://www.rashadataf.com), depending onDOMAIN_NAME
Projects live under projects/. Each project is:
- A standalone app (with its own
package.json, dev server, Dockerfile) - Optionally attached to the shared infra via a project-specific Pulumi stack under
projects/<name>/infra
The example project is projects/hello-world-react.
The Pulumi program at
projects/hello-world-react/infra/Pulumi.yaml:
-
Uses a
StackReferenceto the core stack:coreStack→CORE_STACK_NAME(e.g.your-org/portfolio-infra/dev)- Reads
appNetworkName,domainName,routerEntrypoint,enableTlsfrom infra/Pulumi.yaml outputs
-
Builds a Docker image from the project:
-
Runs a container on the same network as the portfolio
-
Adds Traefik labels so it’s reachable at:
http://hello-world.${DOMAIN_NAME}(dev, no TLS)https://hello-world.${DOMAIN_NAME}(prod, with TLS)
See projects/hello-world-react/infra/Pulumi.yaml for the exact labels and config keys.
-
Ensure core stack is up:
cd infra pulumi stack select dev pulumi up
-
Deploy the hello-world project stack:
cd ../projects/hello-world-react/infra pulumi stack select dev || pulumi stack init dev pulumi up
The dev configuration is in
projects/hello-world-react/infra/Pulumi.dev.yaml and typically sets:hello-world:CORE_STACK_NAME– e.g.your-org/portfolio-infra/devhello-world:APP_PORT– container’s internal port (for prod image this is often80)hello-world:BUILD_PHASE_TARGET–developmentorproduction(Dockerfile target)
-
Access:
- If
DOMAIN_NAME=localhost:http://hello-world.localhost - Otherwise:
http://hello-world.<your-domain>
- If
-
Bring up
portfolio-infraprod stack:cd infra pulumi stack select prod pulumi up
-
Deploy hello-world prod stack:
cd ../projects/hello-world-react/infra pulumi stack select prod || pulumi stack init prod pulumi up
Configure
CORE_STACK_NAMEto point to your prod portfolio stack (e.g.your-org/portfolio-infra/prod) andAPP_PORTto the internal port exposed by the production stage of the Dockerfile (often80). -
Access:
https://hello-world.${DOMAIN_NAME}.
Assume you have a completely separate repo for a project called my-app.
-
Copy the app into
projects/:# In the portfolio repo root mkdir -p projects # Clone / copy your app here git clone <your-my-app-repo> projects/my-app
Or just copy the built project files into
projects/my-app. -
Ensure the app has:
- A Dockerfile that can build and run the project (regardless of language or framework)
- It must expose a clear internal port that your application listens on (e.g.
8080,3000,80, etc.) - Optional: a separate development or build stage if you want different behavior per stack (e.g.
developmentvsproduction)
- It must expose a clear internal port that your application listens on (e.g.
-
Create a Pulumi project under
projects/my-app/infra:The simplest path is to copy and adapt the hello-world infra:
cp -R projects/hello-world-react/infra projects/my-app/infra
Then in
projects/my-app/infra/Pulumi.yaml:-
Change:
-
name: hello-world→name: my-app -
Image name from
hello-world-reactto something likemy-app -
Traefik labels:
- Service name:
traefik.http.services.my-app.loadbalancer.server.port - Routers:
traefik.http.routers.my-app.* - Host rule: e.g.
Host(\my-app.${DOMAIN_NAME}`)`
- Service name:
-
-
Make sure the Docker build
contextpoints to..(the project root).
-
-
Configure the new project stack:
Example dev stack config (
projects/my-app/infra/Pulumi.dev.yaml):config: my-app:APP_PORT: "80" # internal port exposed by your app’s Dockerfile (prod stage) my-app:CORE_STACK_NAME: your-org/portfolio-infra/dev my-app:BUILD_PHASE_TARGET: development # or production
-
Deploy:
-
Core stack (once per environment):
cd infra pulumi up -s dev -
Project stack (per project):
cd ../projects/my-app/infra pulumi up -s dev
You can now update / destroy just
my-appvia its Pulumi stack without touching other projects. -
The example project also includes
projects/hello-world-react/infra-standalone/Pulumi.yaml.
This stack:
- Builds the app Docker image
- Runs it in a single container
- Exposes it directly on a host port (
HOST_PORT, default3001) - Does not need the portfolio-infra stack or Traefik
Usage:
cd projects/hello-world-react/infra-standalone
pulumi stack select dev || pulumi stack init dev
pulumi upApp will be available at the url output (default http://localhost:3001).
You can replicate this pattern for other projects if you need them to run completely standalone.
Contributions are welcome! For major changes, please open an issue first to discuss what you would like to change.
This project is open source and available under the MIT License.
Rashad Ataf – Portfolio
Thank you for visiting my portfolio repository!