Temporal Phase 1 Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Deploy Temporal server infrastructure and create the temporal-workers repository with a working hello-world workflow.
Architecture: Temporal server deployed via Helm (ArgoCD-managed), PostgreSQL on existing CNPG cluster, Worker Controller for rainbow deployments, workers in separate repository.
Tech Stack: Temporal 0.73.1, Python 3.12+, uv, PostgreSQL 16, ArgoCD, External Secrets, Traefik
✅ Phase 1 Complete (2026-01-02)
Section titled “✅ Phase 1 Complete (2026-01-02)”Tracking Issue: selfhosted-cluster#548 (closed)
All Tasks Completed
Section titled “All Tasks Completed”| Task | PR/Commit | Notes |
|---|---|---|
| Task 1: PostgreSQL Database | PR #533 | CNPG Database + ExternalSecret |
| Task 2: Vault Policy | PR #533 | temporal-worker policy + K8s auth role |
| Task 3: Temporal Server | PR #533 | Helm chart via ArgoCD |
| Task 4: Worker Controller | PR #533 | TWC deployed |
| Task 5: temporal-workers repo | — | Repository created |
| Task 6: worker-core package | — | Hello workflow + activity |
| Task 7: Dockerfile + CI | PR #3 | Multi-arch builds (ARM64/AMD64) |
| Task 8: K8s CRDs | — | TemporalWorkerDeployment |
| Task 9: ArgoCD Application | PR #547, #550 | GHCR pull secret + main branch tracking |
| Task 10: Verification | — | All checks passed |
Verification Results
Section titled “Verification Results”| Check | Status | Details |
|---|---|---|
| Worker pod running | ✅ | v0.3.0 running, 0 restarts |
| HelloWorkflow execution | ✅ | "Hello, World!" in 230ms |
| Web UI access | ✅ | temporal.fzymgc.house with Authentik SSO |
Issues Resolved
Section titled “Issues Resolved”| Issue | Resolution |
|---|---|
| temporal-workers#12 | Fixed in v0.3.0 — Dockerfile and uv workspace issues |
| Visibility schema not applied | Created separate temporal_visibility database — single database caused schema version collision (default store v1.18 blocked visibility schema) |
Prerequisites
Section titled “Prerequisites”- Access to fzymgc-house Kubernetes cluster
- Vault access with infrastructure-developer policy
- GitHub CLI authenticated (
gh auth status)
Task 1: Create PostgreSQL Database for Temporal
Section titled “Task 1: Create PostgreSQL Database for Temporal”Files:
- Create:
argocd/app-configs/cnpg/db-temporal.yaml - Create:
argocd/app-configs/cnpg/users-temporal.yaml - Modify:
argocd/app-configs/cnpg/kustomization.yaml
Step 1: Create Vault secrets for Temporal database user
Run:
vault kv put secret/fzymgc-house/cluster/postgres/users/main-temporal \ username=temporal \ password="$(openssl rand -base64 24)"Expected: Success! Data written to: secret/fzymgc-house/cluster/postgres/users/main-temporal
Step 2: Create the database manifest
Create argocd/app-configs/cnpg/db-temporal.yaml:
apiVersion: postgresql.cnpg.io/v1kind: Databasemetadata: name: temporal namespace: postgresspec: cluster: name: main ensure: present name: temporal owner: temporalStep 3: Create the ExternalSecret for database credentials
Create argocd/app-configs/cnpg/users-temporal.yaml:
---apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: main-temporal-credentials namespace: postgresspec: refreshPolicy: Periodic refreshInterval: 5m secretStoreRef: name: vault kind: ClusterSecretStore target: name: main-temporal-credentials creationPolicy: Owner deletionPolicy: Delete template: metadata: labels: cnpg.io/reload: "true" type: kubernetes.io/basic-auth data: username: "{{ .username }}" password: "{{ .password }}" data: - secretKey: username remoteRef: key: fzymgc-house/cluster/postgres/users/main-temporal property: username - secretKey: password remoteRef: key: fzymgc-house/cluster/postgres/users/main-temporal property: passwordStep 4: Add to kustomization
Edit argocd/app-configs/cnpg/kustomization.yaml to add:
resources: # ... existing resources ... - db-temporal.yaml - users-temporal.yamlStep 5: Commit
git add argocd/app-configs/cnpg/db-temporal.yaml \ argocd/app-configs/cnpg/users-temporal.yaml \ argocd/app-configs/cnpg/kustomization.yamlgit commit -m "feat(cnpg): add Temporal database and user credentials"Task 2: Create Vault Policy for Temporal
Section titled “Task 2: Create Vault Policy for Temporal”Files:
- Create:
tf/vault/policy-temporal.tf
Step 1: Create Vault policy for Temporal workers
Create tf/vault/policy-temporal.tf:
# Temporal worker Vault policy# Allows workers to read secrets for Discord, GitHub, S3, etc.
resource "vault_policy" "temporal_worker" { name = "temporal-worker"
policy = <<-EOT # Read Temporal-specific secrets path "secret/data/fzymgc-house/cluster/temporal/*" { capabilities = ["read"] }
# Read Discord bot credentials path "secret/data/fzymgc-house/cluster/discord/*" { capabilities = ["read"] }
# Read GitHub tokens path "secret/data/fzymgc-house/cluster/github/*" { capabilities = ["read"] }
# Read S3/R2 credentials path "secret/data/fzymgc-house/cluster/cloudflare/r2/*" { capabilities = ["read"] } EOT}
# Kubernetes auth role for Temporal workersresource "vault_kubernetes_auth_backend_role" "temporal_worker" { backend = vault_auth_backend.kubernetes.path role_name = "temporal-worker" bound_service_account_names = ["temporal-worker"] bound_service_account_namespaces = ["temporal"] token_policies = [vault_policy.temporal_worker.name] token_ttl = 3600}Step 2: Apply Terraform
Run:
cd tf/vaultterraform plan -out=tfplanterraform apply tfplanExpected: Apply complete! Resources: 2 added
Step 3: Commit
git add tf/vault/policy-temporal.tfgit commit -m "feat(vault): add Temporal worker policy and Kubernetes auth role"Task 3: Create Temporal Server ArgoCD Application
Section titled “Task 3: Create Temporal Server ArgoCD Application”Files:
- Create:
argocd/app-configs/temporal-server/namespace.yaml - Create:
argocd/app-configs/temporal-server/secrets.yaml - Create:
argocd/app-configs/temporal-server/certificate.yaml - Create:
argocd/app-configs/temporal-server/ingress.yaml - Create:
argocd/app-configs/temporal-server/kustomization.yaml - Create:
argocd/cluster-app/templates/temporal-server.yaml
Step 1: Create namespace manifest
Create argocd/app-configs/temporal-server/namespace.yaml:
apiVersion: v1kind: Namespacemetadata: name: temporalStep 2: Create ExternalSecret for database connection
Create argocd/app-configs/temporal-server/secrets.yaml:
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: temporal-db-secret namespace: temporalspec: refreshPolicy: Periodic refreshInterval: 5m secretStoreRef: name: vault kind: ClusterSecretStore target: name: temporal-db-secret creationPolicy: Owner deletionPolicy: Delete data: - secretKey: password remoteRef: key: fzymgc-house/cluster/postgres/users/main-temporal property: passwordStep 3: Create TLS certificate
Create argocd/app-configs/temporal-server/certificate.yaml:
apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: temporal-tls namespace: temporalspec: secretName: temporal-tls issuerRef: name: cloudflare-acme-issuer kind: ClusterIssuer commonName: temporal.fzymgc.house dnsNames: - temporal.fzymgc.house usages: - server authStep 4: Create ingress for Web UI
Create argocd/app-configs/temporal-server/ingress.yaml:
---apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata: name: temporal-auth namespace: temporalspec: forwardAuth: address: http://authentik-server.authentik.svc/outpost.goauthentik.io/auth/traefik trustForwardHeader: true authResponseHeaders: - Remote-User - Remote-Groups - Remote-Email - Remote-Name - X-authentik-username - X-authentik-groups - X-authentik-email - X-authentik-name - X-authentik-uid---apiVersion: traefik.io/v1alpha1kind: IngressRoutemetadata: name: temporal-web namespace: temporal annotations: router-hosts.fzymgc.house/enabled: "true"spec: entryPoints: - websecure routes: - match: Host(`temporal.fzymgc.house`) kind: Rule middlewares: - name: temporal-auth services: - name: temporal-web port: 8080 tls: secretName: temporal-tlsStep 5: Create kustomization
Create argocd/app-configs/temporal-server/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources: - namespace.yaml - secrets.yaml - certificate.yaml - ingress.yamlStep 6: Create ArgoCD Application
Create argocd/cluster-app/templates/temporal-server.yaml:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: temporal-server namespace: argocd annotations: argocd.argoproj.io/sync-wave: "1"spec: project: core-services sources: - chart: temporal repoURL: https://go.temporal.io/helm-charts targetRevision: "0.73.1" helm: releaseName: temporal valuesObject: server: replicaCount: frontend: 1 history: 1 matching: 1 worker: 1 config: persistence: defaultStore: default visibilityStore: visibility numHistoryShards: 128 datastores: default: sql: pluginName: postgres12 driverName: postgres12 databaseName: temporal connectAddr: "main-rw.postgres.svc.cluster.local:5432" connectProtocol: tcp user: temporal existingSecret: temporal-db-secret secretKey: password maxConns: 20 maxIdleConns: 20 maxConnLifetime: "1h" visibility: sql: pluginName: postgres12 driverName: postgres12 databaseName: temporal connectAddr: "main-rw.postgres.svc.cluster.local:5432" connectProtocol: tcp user: temporal existingSecret: temporal-db-secret secretKey: password maxConns: 10 maxIdleConns: 10 maxConnLifetime: "1h"
web: enabled: true replicaCount: 1
admintools: enabled: true
# Disable sub-charts - we use external PostgreSQL cassandra: enabled: false mysql: enabled: false postgresql: enabled: false elasticsearch: enabled: false
# Prometheus metrics prometheus: enabled: true
- repoURL: https://github.com/fzymgc-house/selfhosted-cluster targetRevision: HEAD path: argocd/app-configs/temporal-server destination: server: https://kubernetes.default.svc namespace: temporal syncPolicy: automated: prune: true selfHeal: true syncOptions: - ServerSideApply=true - CreateNamespace=true - RespectIgnoreDifferences=true ignoreDifferences: - kind: ExternalSecret group: external-secrets.io jqPathExpressions: - .spec.data[].remoteRef.conversionStrategy - .spec.data[].remoteRef.decodingStrategy - .spec.data[].remoteRef.metadataPolicyStep 7: Commit
git add argocd/app-configs/temporal-server/ \ argocd/cluster-app/templates/temporal-server.yamlgit commit -m "feat(temporal): add Temporal server ArgoCD application"Task 4: Deploy Worker Controller
Section titled “Task 4: Deploy Worker Controller”Files:
- Create:
argocd/cluster-app/templates/temporal-worker-controller.yaml
Step 1: Create Worker Controller ArgoCD Application
Create argocd/cluster-app/templates/temporal-worker-controller.yaml:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: temporal-worker-controller namespace: argocd annotations: argocd.argoproj.io/sync-wave: "2"spec: project: core-services source: chart: temporal-worker-controller repoURL: oci://docker.io/temporalio/helm-charts targetRevision: "1.0.0" helm: releaseName: temporal-worker-controller valuesObject: # Controller connects to Temporal frontend temporal: address: temporal-frontend.temporal.svc.cluster.local:7233 namespace: workflows destination: server: https://kubernetes.default.svc namespace: temporal syncPolicy: automated: prune: true selfHeal: true syncOptions: - ServerSideApply=trueStep 2: Commit
git add argocd/cluster-app/templates/temporal-worker-controller.yamlgit commit -m "feat(temporal): add Worker Controller for rainbow deployments"Task 5: Create temporal-workers Repository
Section titled “Task 5: Create temporal-workers Repository”Step 1: Create the repository on GitHub
Run:
gh repo create fzymgc-house/temporal-workers \ --private \ --description "Temporal workflow workers for fzymgc-house automation" \ --cloneExpected: Created repository fzymgc-house/temporal-workers
Step 2: Initialize uv workspace
Run:
cd temporal-workersuv init --no-readmeStep 3: Create workspace pyproject.toml
Replace pyproject.toml:
[project]name = "temporal-workers"version = "0.1.0"description = "Temporal workflow workers for fzymgc-house automation"readme = "README.md"requires-python = ">=3.12"
[tool.uv.workspace]members = ["packages/*"]
[tool.ruff]line-length = 100target-version = "py312"
[tool.ruff.lint]select = ["E", "F", "I", "UP", "B", "SIM"]
[tool.pytest.ini_options]asyncio_mode = "auto"testpaths = ["tests"]Step 4: Create directory structure
Run:
mkdir -p packages/common/src/temporal_commonmkdir -p packages/worker-core/src/worker_core/activitiesmkdir -p packages/worker-core/src/worker_core/workflowsmkdir -p testsmkdir -p dockermkdir -p k8s/base k8s/crdsmkdir -p .github/workflowsStep 5: Create common package pyproject.toml
Create packages/common/pyproject.toml:
[project]name = "temporal-common"version = "0.1.0"description = "Shared utilities for Temporal workers"requires-python = ">=3.12"dependencies = [ "temporalio>=1.9.0", "pydantic>=2.0", "pydantic-settings>=2.0", "hvac>=2.0", "opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20", "opentelemetry-exporter-otlp>=1.20", "opentelemetry-instrumentation>=0.40",]
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]packages = ["src/temporal_common"]Step 6: Create common/init.py
Create packages/common/src/temporal_common/__init__.py:
"""Shared utilities for Temporal workers."""
from temporal_common.client import get_temporal_clientfrom temporal_common.settings import Settings
__all__ = ["get_temporal_client", "Settings"]Step 7: Create settings module
Create packages/common/src/temporal_common/settings.py:
"""Pydantic settings for worker configuration."""
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): """Worker configuration loaded from environment."""
model_config = SettingsConfigDict(env_prefix="TEMPORAL_")
# Temporal connection host: str = "localhost:7233" namespace: str = "workflows" task_queue: str = "default"
# Observability otel_endpoint: str = "http://localhost:4317" otel_service_name: str = "temporal-worker"
# Vault (optional, for dynamic secrets) vault_addr: str = "" vault_role: str = "temporal-worker"Step 8: Create client module
Create packages/common/src/temporal_common/client.py:
"""Temporal client factory with OpenTelemetry instrumentation."""
from temporalio.client import Clientfrom temporalio.contrib.opentelemetry import TracingInterceptor
from temporal_common.observability import setup_telemetryfrom temporal_common.settings import Settings
async def get_temporal_client(settings: Settings | None = None) -> Client: """Create a Temporal client with tracing enabled.""" if settings is None: settings = Settings()
interceptor = setup_telemetry(settings)
return await Client.connect( settings.host, namespace=settings.namespace, interceptors=[interceptor] if interceptor else [], )Step 9: Create observability module
Create packages/common/src/temporal_common/observability.py:
"""OpenTelemetry setup for Temporal workers."""
from opentelemetry import tracefrom opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom temporalio.contrib.opentelemetry import TracingInterceptor
from temporal_common.settings import Settings
def setup_telemetry(settings: Settings) -> TracingInterceptor | None: """Configure OpenTelemetry and return Temporal interceptor.""" if not settings.otel_endpoint: return None
resource = Resource.create({"service.name": settings.otel_service_name}) provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint=settings.otel_endpoint, insecure=True) provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
return TracingInterceptor()Step 10: Commit common package
git add packages/common/git commit -m "feat(common): add shared utilities package"Task 6: Create worker-core Package
Section titled “Task 6: Create worker-core Package”Step 1: Create worker-core pyproject.toml
Create packages/worker-core/pyproject.toml:
[project]name = "worker-core"version = "0.1.0"description = "Core Temporal worker with basic activities"requires-python = ">=3.12"dependencies = [ "temporal-common",]
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]packages = ["src/worker_core"]Step 2: Create worker-core init.py
Create packages/worker-core/src/worker_core/__init__.py:
"""Core Temporal worker."""Step 3: Create hello world activity
Create packages/worker-core/src/worker_core/activities/__init__.py:
"""Core activities."""
from worker_core.activities.hello import say_hello
__all__ = ["say_hello"]Create packages/worker-core/src/worker_core/activities/hello.py:
"""Hello world activity for testing."""
from temporalio import activity
@activity.defnasync def say_hello(name: str) -> str: """Simple hello world activity.""" activity.logger.info(f"Saying hello to {name}") return f"Hello, {name}!"Step 4: Create hello world workflow
Create packages/worker-core/src/worker_core/workflows/__init__.py:
"""Core workflows."""
from worker_core.workflows.hello import HelloWorkflow
__all__ = ["HelloWorkflow"]Create packages/worker-core/src/worker_core/workflows/hello.py:
"""Hello world workflow for testing."""
from datetime import timedelta
from temporalio import workflow
with workflow.unsafe.imports_passed_through(): from worker_core.activities import say_hello
@workflow.defnclass HelloWorkflow: """Simple hello world workflow."""
@workflow.run async def run(self, name: str) -> str: """Execute the workflow.""" return await workflow.execute_activity( say_hello, name, start_to_close_timeout=timedelta(seconds=10), )Step 5: Create worker main entry point
Create packages/worker-core/src/worker_core/main.py:
"""Worker entry point."""
import asyncioimport logging
from temporalio.worker import Worker
from temporal_common import Settings, get_temporal_clientfrom worker_core.activities import say_hellofrom worker_core.workflows import HelloWorkflow
logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
async def main() -> None: """Run the worker.""" settings = Settings() logger.info(f"Starting worker, connecting to {settings.host}")
client = await get_temporal_client(settings)
worker = Worker( client, task_queue=settings.task_queue, workflows=[HelloWorkflow], activities=[say_hello], )
logger.info(f"Worker started on task queue: {settings.task_queue}") await worker.run()
if __name__ == "__main__": asyncio.run(main())Step 6: Commit worker-core package
git add packages/worker-core/git commit -m "feat(worker-core): add hello world workflow and activity"Task 7: Create Dockerfile and GitHub Actions
Section titled “Task 7: Create Dockerfile and GitHub Actions”Step 1: Create Dockerfile for worker-core
Create docker/Dockerfile.core:
# syntax=docker/dockerfile:1FROM python:3.12-slim AS builder
# Install uvCOPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
# Copy workspace filesCOPY pyproject.toml uv.lock ./COPY packages/common packages/commonCOPY packages/worker-core packages/worker-core
# Install dependenciesRUN uv sync --frozen --no-dev
FROM python:3.12-slim
WORKDIR /app
# Copy installed packages from builderCOPY --from=builder /app/.venv /app/.venv
# Set PATH to use venvENV PATH="/app/.venv/bin:$PATH"
# Copy application codeCOPY packages/common/src packages/common/srcCOPY packages/worker-core/src packages/worker-core/src
# Run workerCMD ["python", "-m", "worker_core.main"]Step 2: Create GitHub Actions workflow
Create .github/workflows/build.yaml:
name: Build and Push Workers
on: push: branches: [main] paths: - 'packages/**' - 'docker/**' - '.github/workflows/build.yaml' pull_request: branches: [main]
env: REGISTRY: ghcr.io IMAGE_PREFIX: ghcr.io/${{ github.repository }}
jobs: build: runs-on: ubuntu-latest strategy: matrix: worker: [core] permissions: contents: read packages: write
steps: - uses: actions/checkout@v4
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
- name: Log in to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_PREFIX }}/${{ matrix.worker }} tags: | type=sha,prefix= type=ref,event=branch type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile.${{ matrix.worker }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=maxStep 3: Create .gitignore
Create .gitignore:
# Python__pycache__/*.py[cod]*$py.class*.so.Python.venv/venv/ENV/.uv/
# Testing.pytest_cache/.coveragehtmlcov/
# IDE.idea/.vscode/*.swp*.swo
# OS.DS_StoreThumbs.db
# Builddist/build/*.egg-info/Step 4: Create README
Create README.md:
# Temporal Workers
Temporal workflow workers for fzymgc-house automation.
## Development
```bash# Install dependenciesuv sync
# Run worker locally (requires local Temporal server)temporal server start-dev &uv run python -m worker_core.mainPackages
Section titled “Packages”common- Shared utilities (client, settings, observability)worker-core- Core activities (Discord, GitHub, S3)worker-terraform- Terraform operations (planned)worker-llm- LLM/Agent activities (planned)
**Step 5: Sync and commit**
```bashuv syncgit add docker/ .github/ .gitignore README.md uv.lockgit commit -m "feat: add Dockerfile and GitHub Actions workflow"Step 6: Push to GitHub
git push -u origin mainTask 8: Create TemporalWorkerDeployment CRD
Section titled “Task 8: Create TemporalWorkerDeployment CRD”Files:
- Create:
k8s/base/serviceaccount.yaml - Create:
k8s/crds/worker-core.yaml - Create:
k8s/base/kustomization.yaml - Create:
k8s/crds/kustomization.yaml
Step 1: Create service account
Create k8s/base/serviceaccount.yaml:
apiVersion: v1kind: ServiceAccountmetadata: name: temporal-worker namespace: temporalStep 2: Create worker CRD
Create k8s/crds/worker-core.yaml:
apiVersion: temporal.io/v1alpha1kind: TemporalWorkerDeploymentmetadata: name: worker-core namespace: temporalspec: deploymentName: worker-core image: ghcr.io/fzymgc-house/temporal-workers/core:latest temporalNamespace: workflows taskQueues: - default serviceAccountName: temporal-worker env: - name: TEMPORAL_HOST value: temporal-frontend.temporal.svc.cluster.local:7233 - name: TEMPORAL_NAMESPACE value: workflows - name: TEMPORAL_TASK_QUEUE value: default - name: TEMPORAL_OTEL_ENDPOINT value: alloy.alloy.svc.cluster.local:4317 resources: limits: memory: 512Mi requests: memory: 256Mi cpu: 100m rollout: strategy: Progressive steps: - rampPercentage: 50 pauseDuration: 1m scaling: minReplicas: 1 maxReplicas: 3Step 3: Create kustomization files
Create k8s/base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources: - serviceaccount.yamlCreate k8s/crds/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources: - ../base - worker-core.yamlStep 4: Commit and push
git add k8s/git commit -m "feat(k8s): add TemporalWorkerDeployment CRD for worker-core"git pushTask 9: Create ArgoCD Application for Workers
Section titled “Task 9: Create ArgoCD Application for Workers”Files (in selfhosted-cluster repo):
- Create:
argocd/app-configs/temporal-workers/secrets.yaml - Create:
argocd/app-configs/temporal-workers/kustomization.yaml - Create:
argocd/cluster-app/templates/temporal-workers.yaml
Step 1: Switch to selfhosted-cluster repo
Run:
cd /Volumes/Code/github.com/fzymgc-house/selfhosted-clusterStep 2: Create worker secrets ExternalSecret
Create argocd/app-configs/temporal-workers/secrets.yaml:
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: temporal-worker-secrets namespace: temporalspec: refreshPolicy: Periodic refreshInterval: 5m secretStoreRef: name: vault kind: ClusterSecretStore target: name: temporal-worker-secrets creationPolicy: Owner deletionPolicy: Delete data: - secretKey: DISCORD_BOT_TOKEN remoteRef: key: fzymgc-house/cluster/discord/bot property: token - secretKey: GITHUB_TOKEN remoteRef: key: fzymgc-house/cluster/github/api property: tokenStep 3: Create kustomization
Create argocd/app-configs/temporal-workers/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources: - secrets.yamlStep 4: Create ArgoCD Application
Create argocd/cluster-app/templates/temporal-workers.yaml:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: temporal-workers namespace: argocd annotations: argocd.argoproj.io/sync-wave: "3"spec: project: core-services sources: # Worker CRDs from temporal-workers repo - repoURL: https://github.com/fzymgc-house/temporal-workers targetRevision: HEAD path: k8s/crds # Secrets from selfhosted-cluster - repoURL: https://github.com/fzymgc-house/selfhosted-cluster targetRevision: HEAD path: argocd/app-configs/temporal-workers destination: server: https://kubernetes.default.svc namespace: temporal syncPolicy: automated: prune: true selfHeal: true syncOptions: - ServerSideApply=true ignoreDifferences: - kind: ExternalSecret group: external-secrets.io jqPathExpressions: - .spec.data[].remoteRef.conversionStrategy - .spec.data[].remoteRef.decodingStrategy - .spec.data[].remoteRef.metadataPolicyStep 5: Commit
git add argocd/app-configs/temporal-workers/ \ argocd/cluster-app/templates/temporal-workers.yamlgit commit -m "feat(temporal): add ArgoCD application for Temporal workers"Task 10: Create Temporal Namespace and Test
Section titled “Task 10: Create Temporal Namespace and Test”Step 1: Push all changes
Run:
git pushStep 2: Wait for ArgoCD sync
Run:
kubectl --context fzymgc-house get applications -n argocd | grep temporalExpected: Applications syncing/synced
Step 3: Create workflows namespace in Temporal
Run:
kubectl --context fzymgc-house exec -it -n temporal deploy/temporal-admintools -- \ temporal operator namespace create --namespace workflowsExpected: Namespace workflows successfully registered.
Step 4: Run hello world workflow
Run:
kubectl --context fzymgc-house exec -it -n temporal deploy/temporal-admintools -- \ temporal workflow execute \ --namespace workflows \ --task-queue default \ --type HelloWorkflow \ --input '"World"'Expected: Result: "Hello, World!"
Step 5: Verify in Web UI
Open: https://temporal.fzymgc.house
Expected: See completed HelloWorkflow execution
Verification Checklist
Section titled “Verification Checklist”- Database created in CNPG
- Vault policy applied
- Temporal server pods running
- Worker Controller pod running
- temporal-workers repo created with CI
- Worker image pushed to GHCR (multi-arch)
- Worker pod running via Controller (v0.3.0)
- HelloWorkflow executes successfully (
"Hello, World!") - Web UI accessible with auth (Authentik SSO)
Phase 2 Planning
Section titled “Phase 2 Planning”With Phase 1 complete, potential next steps include:
- Add real workflows — Discord notifications, GitHub automation, S3 operations
- Create Authentik application — Restrict Web UI access to specific groups (
tf/authentik/temporal.tf) - Add observability — Temporal metrics to Grafana dashboards
- Worker scaling — Configure HPA based on task queue depth
- Documentation — Add Temporal operations guide to
docs/operations/temporal.md