From cc2df9ea6b71e1ddae4bf87d5bd926f0867cecef Mon Sep 17 00:00:00 2001 From: qpismont Date: Sat, 25 Jul 2026 15:54:53 +0000 Subject: [PATCH] Add tempo + portfolio --- .devcontainer/devcontainer.json | 2 +- Caddyfile | 3 +- docker-compose.core.yml | 5 + docker-compose.observability.yml | 17 + otel-collector/config.yaml | 8 +- portfolio/index.html | 586 +++++++++++++++++++++++++++++++ tempo/tempo.yml | 24 ++ wireguard/CoreDns | 1 + 8 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 portfolio/index.html create mode 100644 tempo/tempo.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 655be80..1ab514f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,5 +7,5 @@ "build": { "dockerfile": "Dockerfile" }, - "postAttachCommand": "docker context create prod --docker \"host=ssh://user@62.210.212.10\"" + "postCreateCommand": "docker context create prod --docker \"host=ssh://user@62.210.212.10\"" } diff --git a/Caddyfile b/Caddyfile index 6715bf7..0cd0e16 100644 --- a/Caddyfile +++ b/Caddyfile @@ -13,7 +13,8 @@ qpismont.fr { } www.qpismont.fr { - reverse_proxy my_cv:80 + root * /www/html + file_server } gitea.qpismont.fr { diff --git a/docker-compose.core.yml b/docker-compose.core.yml index d255a87..df7afbc 100644 --- a/docker-compose.core.yml +++ b/docker-compose.core.yml @@ -10,6 +10,8 @@ services: configs: - source: caddy_config target: /etc/caddy/Caddyfile + - source: caddy_portfolio_html + target: /www/html/index.html volumes: - reversecaddydata:/data - reversecaddyconfig:/config @@ -152,6 +154,9 @@ configs: wireguard_coredns_config: name: wireguard_coredns_config_${DATETIME} file: ./wireguard/CoreDns + caddy_portfolio_html: + name: caddy_portfolio_html_${DATETIME} + file: ./portfolio/index.html volumes: giteadata: diff --git a/docker-compose.observability.yml b/docker-compose.observability.yml index 15ac3a2..2028543 100644 --- a/docker-compose.observability.yml +++ b/docker-compose.observability.yml @@ -1,6 +1,19 @@ version: "3" services: + tempo: + image: grafana/tempo:2.10.7 + command: + - -config.file=/etc/tempo/tempo.yml + configs: + - source: tempo_config + target: /etc/tempo/tempo.yml + volumes: + - tempodata:/var/tempo + networks: + - prometheus_network + - wireguard_network + perses: image: persesdev/perses:latest volumes: @@ -94,12 +107,16 @@ configs: otel_collector_config: name: otel_collector_config_${DATETIME} file: ./otel-collector/config.yaml + tempo_config: + name: tempo_config_${DATETIME} + file: ./tempo/tempo.yml volumes: persesdata: prometheusdata: ntfydata: alertmanagerdata: + tempodata: networks: reverse_network: diff --git a/otel-collector/config.yaml b/otel-collector/config.yaml index 9f05a33..3d398b2 100644 --- a/otel-collector/config.yaml +++ b/otel-collector/config.yaml @@ -27,10 +27,16 @@ processors: exporters: prometheusremotewrite: endpoint: http://prometheus:9090/api/v1/write + otlphttp/tempo: + endpoint: http://tempo:4318 service: pipelines: metrics: receivers: [otlp, prometheus] processors: [memory_limiter, batch] - exporters: [prometheusremotewrite] \ No newline at end of file + exporters: [prometheusremotewrite] + traces: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [otlphttp/tempo] \ No newline at end of file diff --git a/portfolio/index.html b/portfolio/index.html new file mode 100644 index 0000000..f72d7d6 --- /dev/null +++ b/portfolio/index.html @@ -0,0 +1,586 @@ + + + + + + +Portfolio — Platform Engineer + + + + + + + + +
+
+
Open to opportunities
+

I design systems
end to end, built to hold up in production.

+

+ Platform / infrastructure engineer. From code to production, through CI/CD, + observability and security — an integrated ecosystem designed to be operated + over the long run. +

+ +
+
+ +
+
+
+ 01 +

About

+
+
+
+

+ Developer for over 20 years (first lines of C at age 10), + working as an engineer for 10 years. My specialty: designing coherent + distributed systems and keeping them operable over time. +

+

+ What I show here is infrastructure + actually running in production for my daily use, tested under failure, + with full observability and an end-to-end CI/CD chain. +

+

+ On the people side, I mentored two interns from the ground up on our stack — + both were hired at the end of their internship. Helping others level up + is part of how I approach the craft. +

+

+ My areas of focus: Rust, container orchestration, + security / isolation, networking, and self-hosted observability. +

+
+
+
Principles
+
    +
  • If it hasn't been tested under failure, it isn't resilient.
  • +
  • A technical choice is justified by its tradeoffs, not by trends.
  • +
  • Migrate for value, never for dogma.
  • +
  • Security is designed starting from the threat model.
  • +
  • A system must be observable before it's scaled.
  • +
+
+
+
+
+ +
+
+
+ 02 +

Selected projects

+
+

+ Three pieces that, together, cover the essentials of a platform profile: + operating a system, designing a secure architecture, and thinking about scale. +

+ +
+
+

🏗️ Self-hosted infrastructure Complete & tested

+
+

A complete ecosystem — code → CI → production → observability — on bare metal, validated by real failure tests.

+
+
The problem
+

Having a reliable personal platform to host and operate my own services, with the standards of real production: resilience, alerting, zero data loss.

+
+
+
The architecture
+
    +
  • Networking — WireGuard (the only entry point), Docker overlay, and CoreDNS for *.internal resolution with fallback to Docker's DNS.
  • +
  • CI/CD — Gitea + Woodpecker: lint → tests → build → automatic push registry on tag/release → deploy on prod.
  • +
  • Observability — OtelCollector (central hub), Prometheus (storage + alerting), Loki (logs) + Tempo (traces), Perses dashboards, Alertmanager → ntfy.
  • +
  • Safety net — Blackbox scraped directly by Prometheus: monitoring survives a Collector failure.
  • +
+
+
+
What sets it apart — validated resilience
+
    +
  • Gitea outage → ntfy alert received ✓
  • +
  • OtelCollector outage → alert received, monitoring maintained ✓
  • +
  • Multiple redeployments → zero data loss (persistent volumes) ✓
  • +
+
+
+ Docker SwarmWireGuardCoreDNSGiteaWoodpecker CIOpenTelemetryPrometheusLokiTempoPersesAlertmanager +
+
+ +
+
+

🤖 Herald — AI agent for Gitea In active development

+
+

A code-review and AI-assistance bot for Gitea, written in Rust, with a security-first agentic architecture.

+
+
The problem
+

No AI assistance solution exists for Gitea. A real need: solo code review, implementing tests/features, usable day to day — including from a phone.

+
+
+
The security model
+

The challenge of an agent that executes code isn't making it run, but making it run without risk of exfiltration or compromise. The decisions:

+
    +
  • Agent loop on the host, side effects isolated in a container
  • +
  • Git handled exclusively by the host (Gitea token): no secret ever touches the sandbox.
  • +
  • .git "disarmed" for reading (remotes removed, credentials cleared, hooks emptied) — on a copy, with the armed original staying on the host.
  • +
  • Network cut during the AI phase (disconnect via bollard): exfiltration is impossible. Network active only for postCreate.
  • +
  • Persistence from the start (resume after stop).
  • +
+
+
+
Roadmap (intentional semver)
+
    +
  • 1.0 — Review + comments posted to the right line on the MR
  • +
  • 1.1 — The 3 observability signals
  • +
  • 1.2 — Sandboxing begins (read-only first)
  • +
  • 2.0 — Feature creation (write / execute)
  • +
  • 2.x — Advanced persistence + web config dashboard
  • +
+
+
+ RustTokiobollardDevContainersOpenRouterOpenTelemetry +
+
+ +
+
+

⚡ Distributed media pipeline Design / upcoming

+
+

An extended imgproxy-style media processing pipeline, designed for horizontal scaling and streaming.

+
+
The problem
+

Process media (images, streaming) compositionally, with an architecture able to scale out horizontally — and serve as a real load-testing playground.

+
+
+
The architecture
+
    +
  • Compositional API: a core + runners communicating over NATS.
  • +
  • Multi-level cache, horizontal scaling of runners.
  • +
  • Claim-check pattern: files transit through an object store (S3/Minio); NATS carries only commands and metadata.
  • +
+
+
+
The technical goal
+

An ideal candidate for load tests (k6) instrumented by my observability stack — validating system behavior at scale and documenting the breaking points.

+
+
+ RustTokioNATSS3 / Miniok6OpenTelemetryDocker Swarm +
+
+
+
+ +
+
+
+ 03 +

Skills

+
+
+
+

Languages

+

Rust (async / Tokio), TypeScript / Node. Looking for the right tool for the right problem.

+
+
+

Infrastructure & Ops

+

Docker Swarm, bare metal, Fedora Bootc (immutable OS), CI/CD (Gitea, Woodpecker).

+
+
+

Observability

+

OpenTelemetry, Prometheus, Loki, Tempo, Perses, Alertmanager. Full stack, tested under failure.

+
+
+

Networking

+

WireGuard, CoreDNS, overlay networks, Caddy. Designing self-hosted private networks.

+
+
+

Security & Isolation

+

Container sandboxing, privilege separation (DBus / Polkit), threat modeling.

+
+
+

Distributed & Messaging

+

NATS, distributed architectures, claim-check patterns, horizontal scaling.

+
+
+
+
+ +
+
+
+ 04 +

Contact

+
+

+ Interested in platform / infrastructure / staff roles where technical quality + and autonomy matter. The code for everything shown here is available to review. +

+ +
+
+ + + + \ No newline at end of file diff --git a/tempo/tempo.yml b/tempo/tempo.yml new file mode 100644 index 0000000..33b567a --- /dev/null +++ b/tempo/tempo.yml @@ -0,0 +1,24 @@ +server: + http_listen_port: 3200 + +distributor: + receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + +ingester: + max_block_duration: 5m + +compactor: + compaction: + block_retention: 48h + +storage: + trace: + backend: local + local: + path: /var/tempo/traces + wal: + path: /var/tempo/wal \ No newline at end of file diff --git a/wireguard/CoreDns b/wireguard/CoreDns index 1dc1526..7775cf4 100644 --- a/wireguard/CoreDns +++ b/wireguard/CoreDns @@ -1,6 +1,7 @@ .:53 { rewrite name perses.internal perses rewrite name prometheus.internal prometheus + rewrite name tempo.internal tempo forward . 127.0.0.11 errors cache 10