Docker & CI/CD ============== Container deployment -------------------- Build the image: .. code-block:: dockerfile # Dockerfile FROM python:3.12-slim RUN pip install metapod-probes[all] WORKDIR /app ENTRYPOINT ["metapod"] .. code-block:: bash docker build -t metapod . Run with mounted config and output: .. code-block:: bash docker run --rm \ -v $(pwd)/config.yaml:/app/config.yaml \ -v $(pwd)/output:/app/output \ metapod run "oracle.metadata.*" --config /app/config.yaml Docker Compose -------------- .. code-block:: yaml # docker-compose.yml version: "3.8" services: metapod: build: . volumes: - ./config.yaml:/app/config.yaml:ro - ./output:/app/output environment: - ORACLE_PASSWORD=${ORACLE_PASSWORD} command: run "oracle.metadata.*" --config /app/config.yaml --delta # Optional: daemon mode metapod-daemon: build: . ports: - "9090:9090" volumes: - ./config.yaml:/app/config.yaml:ro command: serve --config /app/config.yaml --port 9090 GitHub Actions -------------- .. code-block:: yaml # .github/workflows/metadata.yml name: Metadata Extraction on: schedule: - cron: "0 6 * * 1-5" # Weekdays at 6 AM workflow_dispatch: # Manual trigger jobs: extract: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install metapod run: pip install metapod-probes[oracle,azure] - name: Extract metadata env: ORACLE_PASSWORD: ${{ secrets.ORACLE_PASSWORD }} run: metapod run "oracle.metadata.*" --config config.yaml --delta - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: metadata-${{ github.run_number }} path: output/ retention-days: 30 # Optional: push to catalog - name: Push to Dataflix if: always() env: DATAFLIX_API_KEY: ${{ secrets.DATAFLIX_API_KEY }} run: | metapod push \ --target https://dataflix.example.com \ --project production \ --api-key $DATAFLIX_API_KEY GitLab CI --------- .. code-block:: yaml # .gitlab-ci.yml metadata-extraction: image: python:3.12-slim stage: extract rules: - if: $CI_PIPELINE_SOURCE == "schedule" script: - pip install metapod-probes[oracle] - metapod run "oracle.metadata.*" --config config.yaml --delta artifacts: paths: - output/ expire_in: 30 days variables: ORACLE_PASSWORD: $ORACLE_PASSWORD Kubernetes CronJob ------------------ .. code-block:: yaml # k8s/cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata: name: metapod-extraction spec: schedule: "0 6 * * *" jobTemplate: spec: template: spec: containers: - name: metapod image: metapod:latest command: - metapod - run - "oracle.metadata.*" - --config - /config/config.yaml - --delta - --output - /output volumeMounts: - name: config mountPath: /config - name: output mountPath: /output envFrom: - secretRef: name: metapod-secrets volumes: - name: config configMap: name: metapod-config - name: output persistentVolumeClaim: claimName: metapod-output restartPolicy: OnFailure Credential management --------------------- Never put credentials in config files. Use: - **Environment variables**: ``ORACLE_PASSWORD``, ``AZURE_CLIENT_SECRET`` - **CI/CD secrets**: GitHub Secrets, GitLab CI Variables, Azure Key Vault - **.env file** (local dev only, add to ``.gitignore``) - **Kubernetes secrets**: mounted as env vars or files Example ``.env``: .. code-block:: bash # .env (add to .gitignore!) ORACLE_PASSWORD=my-secret AZURE_TENANT_ID=xxxx AZURE_CLIENT_ID=xxxx AZURE_CLIENT_SECRET=xxxx metapod loads ``.env`` automatically via python-dotenv.