Generally Available — contact us for access

The Self-Hosted M365 Orchestrator

GitOps-driven deployment and management for Microsoft 365. Define your tenant configuration as code, deploy through approval-gated pipelines, and maintain full history across every client.

What is CONFIG365?

CONFIG365 is a Configuration-as-Code solution for Microsoft 365. It enables MSPs and IT teams to:

  • Define M365 configurations as JSON files in Git repositories
  • Deploy configurations via Gitea Actions workflows with approval gates
  • Use the same baseline across unlimited tenants
  • Get clear, actionable feedback on every deployment
Hosted Web Application · Bundled with CONFIG365

Management Portal

The CONFIG365 Management Portal is a full-featured, server-rendered web application bundled directly into the CONFIG365 container. Deploy baselines, inspect drift, manage apps and groups, configure Conditional Access sync, and automate nightly maintenance — all from a single, polished web UI backed by a Next.js server that talks to Gitea's API and manages tenant credentials securely. No CLI, no JSON editing, no extra software to install.

Management portal, Gitea, runner, and token-api — all in one container.

CONFIG365 ships as a single OCI container. The Next.js management portal, Gitea (Git server + Actions CI), act_runner, and the internal token-api all run together — deploy to Azure Container Apps, Azure App Service, or any Docker host. Credentials are encrypted at rest in SQLite and never leave your container.

Portal Views

Tenant Dashboard

Multi-tenant overview with pipeline status, last backup time, and one-click deploy or approve per client.

WhatIf Preview

Review every create, update, and delete before it applies. Approve or reject with full diff context.

Policy Viewer

Browse tenant backups with filename and content search, resizable file panel, and one-click promote to baseline — including large repos with 2000+ backup files.

Baseline Viewer

Compare a live tenant backup against the shared baseline. Surface deviations and conflicting tenant-only overrides.

Timeline

Full deployment history with per-commit diffs. Restore any resource to a previous state directly from the UI.

App Deployment

Deploy Win32 apps via Chocolatey or WinGet, push mobile apps through Intune, and manage enterprise app registrations.

Group Management

Define Entra ID groups as JSON with dynamic membership rules. Baseline-group targeting restricts policies to the right tenant set.

CA Options

Per-tenant Conditional Access sync overrides. Set global policy state, or let individual tenants preserve custom CA configurations.

Maintenance Tasks

Run scheduled or on-demand operations: group splits, Exchange font defaults, GAL visibility, and Intune device renaming.

OS Version Control

Set minimum OS thresholds for Android, iOS, Windows, and macOS. Latest versions fetched automatically; apply updates across all tenants in one operation.

Automatic · No manual sync

License-Based Policy Assignment

Policy groups can automatically include tenants based on their Microsoft 365 license subscriptions — no manual list maintenance required. Define a license rule once in the baseline and every qualifying tenant receives the right policies on their next deploy, even as licenses change over time.

01

Daily Backup

The backup pipeline exports each tenant's subscribed SKUs to backups/licenses/subscribed-skus.json — alongside groups, policies, and Intune configs.

02

License Rules in Baseline

Each policy group in groups-config.json defines which SKU part numbers qualify. E.g. SPE_E3 or ENTERPRISEPREMIUM for an Enterprise CA group.

03

Automatic at Deploy

Resolve-TenantGroups.ps1 reads the backed-up SKU data and evaluates rules live. Qualifying tenants receive the group's extra content — no YAML edits, no manual assignment.

Rule defined in baseline

{
  "Enterprise": {
    "membership": {
      "dynamic": [{
        "type": "license",
        "skuPartNumbers": [
          "SPE_E3",
          "SPE_E5",
          "ENTERPRISEPREMIUM"
        ]
      }]
    },
    "content": {
      "folders": [
        "conditional-access/enterprise"
      ]
    }
  }
}

At deploy time

## Resolve-TenantGroups.ps1

Loaded 12 subscribed SKU(s)

Dynamic match: 'Enterprise'

└ license rule matched SKU

└ ENTERPRISEPREMIUM

Extra folder applied:

conditional-access/enterprise/

Zero maintenance: When a tenant upgrades from Business Premium to E3, their next scheduled deploy automatically picks up the Enterprise policies — no portal action needed.

Evaluated at

Deploy time

No pre-sync required

License data source

Tenant backup

backups/licenses/subscribed-skus.json

Extensible to

Any rule type

license · domain · country · …

System Architecture

CONFIG365 Architecture — Gitea repositories, Gitea Actions pipeline workflow, and Microsoft 365 tenant deployment
Click to enlarge

GitOps Approach

CONFIG365 follows a GitOps model where configurations are stored as JSON files in Git repositories and deployed through CI/CD pipelines. This gives you:

Version Control

Every change tracked in Git history with author and timestamp.

Audit Trail

Who changed what, when, and why — always available.

Rollback Capability

Revert to any previous configuration state with a single commit.

Review Process

Pull requests for changes before they reach the pipeline.

Approval Gates

Human approval required before any configuration is applied.

Security Model

CONFIG365 runs as a single OCI container — on Azure Container Apps, Azure App Service, or any Docker host. The management portal, Gitea, act_runner, and token-api all run inside the same container. No third-party SaaS ever touches your tenant credentials or M365 configurations.

The Orchestrator

  • Gitea Actions jobs executed by act_runner running in your own container (host mode)
  • PowerShell scripts call Microsoft Graph directly using tokens fetched from the internal token-api
  • Org-level Gitea secrets store credentials — scoped to your MSP org, never exposed to tenants
  • Workflow YAML lives in your Gitea repositories — version-controlled and fully auditable

The Management Portal

  • Next.js server-rendered app with iron-session authentication — runs inside the CONFIG365 container
  • Tenant credentials encrypted at rest in SQLite on your own volume — never leave your infrastructure
  • Internal token-api on localhost:4322 handles delegated auth flows without exposing secrets in workflow YAML
  • MSP staff log in via the portal — no Gitea UI access or CLI skills required

Your infrastructure, your data.

CONFIG365 credentials — Entra ID app client secrets, tenant IDs, and delegated tokens — are stored exclusively in your container's encrypted SQLite database on your own persistent volume. The only parties that ever touch your credentials are your CONFIG365 instance and Microsoft Graph.

Multi-Tenant Architecture

For MSPs managing multiple tenants, CONFIG365 supports a shared baseline with per-tenant repositories. Each tenant repo inherits shared configurations while maintaining tenant-specific overrides and assignments.

baseline-repo

Shared configurations

Tenant-acme
Tenant-contoso
Tenant-fabrikam

Deployment Workflow

Every deployment follows a safe, predictable three-stage workflow:

1

WhatIf Preview

All scripts run with the -WhatIf flag. You see exactly what will happen before anything changes.

## Processing Intune Configurations

→ Would CREATE: 5 policies

→ Would UPDATE: 12 policies

○ No changes: 83 policies

2

Approval Gate

Pipeline pauses and waits for human approval. Approve directly from the CONFIG365 portal dashboard.

Waiting for approval (production)
3

Apply Changes

After approval, configurations are applied via Microsoft Graph API. Each resource reports its status.

✓ Created: Windows Defender Antivirus

✓ Updated: BitLocker Encryption

✓ Assignments applied: 8 policies

○ No changes: 83 policies

##[command] All Intune policies configured successfully!

4

Automated Backup

Immediately after a successful deployment, the backup pipeline runs automatically — exporting the full current configuration state and committing it to the tenant's own Git repository as a timestamped snapshot. Because every backup is a Git commit, the repo builds up a complete, permanent history of the tenant's configuration over time. Any past state is instantly recoverable.

##[section] Post-Deployment Backup

Exporting Conditional Access policies...

Exporting Intune configurations...

Exporting Groups, Exchange, Teams...

✓ Backup committed to backups/ (42 files)

Scoped Deployments

By default a deploy run is fully scoped — all enabled categories are planned and applied. The deployOptions pipeline parameter lets you restrict a run to a specific combination of operations and policy categories, so you can, for example, delete-only without touching any create or update logic.

Operation flags

Flag Effect
allowCreate Configure-*.ps1 scripts may create new resources that are in the baseline but missing from the tenant.
allowUpdate Configure-*.ps1 scripts may update resources that already exist but differ from the baseline.
allowDelete Remove-*.ps1 scripts run and delete resources listed in baseline-remove/ or pending-removes/.

Important: Configure (create/update) steps are gated on allowCreate or allowUpdate. Passing only allowDelete skips all Configure steps entirely — no unintended creates will run.

Category flags

Category flags select which policy types the pipeline processes. At least one must be present alongside the operation flags.

deployIntunedeployConditionalAccessdeployGroupsdeployCustomAttributesdeployEntraIdDeviceSettingsdeployTeamsdeploySharePointdeploySharePointSettingsdeployInformationProtectiondeployExchangedeployAppsChocolateydeployAppsWingetdeployAppsCustomdeployEnterpriseAppsdeployAuthenticationPoliciesdeployEntraIdConsentpermissions

Behaviour matrix

deployOptions value Step runs? Creates? Updates? Deletes?
allowCreate,allowUpdate,allowDelete,deployIntune ✓ ✓ ✓ ✓
allowCreate,allowUpdate,deployIntune ✓ ✓ ✓ —
allowCreate,deployIntune ✓ ✓ — —
allowUpdate,deployIntune ✓ — ✓ —
allowDelete,deployIntune — — — ✓

The Create/Update columns reflect what the script does — controlled by ALLOW_CREATE / ALLOW_UPDATE env vars passed into each Configure-*.ps1.

Example — delete-only run for Intune and Conditional Access

# Gitea Actions workflow dispatch (manual override)
deployOptions: allowDelete,deployIntune,deployConditionalAccess

# ↳ Configure-Intune.ps1            SKIPPED  (no allowCreate / allowUpdate)
# ↳ Configure-ConditionalAccess.ps1 SKIPPED  (no allowCreate / allowUpdate)
# ↳ Remove-Intune.ps1               RUNS     (allowDelete + deployIntune)
# ↳ Remove-ConditionalAccess.ps1    RUNS     (allowDelete + deployConditionalAccess)

Deployment Feedback

CONFIG365 provides detailed, actionable feedback for every policy:

deployment output

##[section] Summary

Total policies: 120 (118 processed, 2 blocked)

✓ Created: 5

✓ Updated: 12

✓ Assignments applied: 8

○ No changes: 93

✗ BLOCKED: 2 (missing groups)

##[error] BLOCKED POLICIES - Cannot deploy due to missing dependencies:

✗ [settings-catalog] New Feature Policy

Missing: Baseline - Feature Pilot Users

By Policy Type:

✓ settings-catalog: 3 created, 5 updated, 42 unchanged

✓ compliance-policies: 1 created, 2 updated, 15 unchanged

✓ device-configurations: 1 created, 5 updated, 36 unchanged

═══════════════════════════════════════════════════

DETAILED CHANGES

═══════════════════════════════════════════════════

┌─ [settings-catalog] Windows Defender Settings

│ defenderCloudBlockLevel: 'high' → 'highPlus'

│ Description: 'Old desc' → 'Updated description'

└─────────────────────────────────────────────────

Handling Errors

CONFIG365 catches common issues before they become problems:

Missing Groups

If a policy references a group that doesn't exist, the deployment is blocked with a clear message:

BLOCKED: Cannot deploy "New Policy" - missing group "Baseline - New Users"

API Errors

Graph API errors are captured with full details, not hidden behind generic messages:

Failed: Windows Update Ring

Code: BadRequest

Message: Property 'featureUpdatesDeferralInDays' must be between 0 and 365

Continue on Error

When one policy fails, CONFIG365 continues processing the rest. You get a complete summary at the end showing all successes and failures.

Backup Pipeline

A dedicated backup pipeline runs automatically each night at 2 AM UTC, exporting every client's live M365 configuration to their Git repository as plain JSON. The same resource types that can be deployed can also be backed up — making point-in-time restore as simple as copying files from a past commit back into the baseline and re-running the deploy pipeline.

Supported resource types — deploy & backup

✓ Conditional Access policies
✓ Named locations
✓ Security groups
✓ Intune settings catalog
✓ Intune compliance policies
✓ Intune app protection (MAM)
✓ Autopilot profiles
✓ Intune filters
✓ Authentication methods
✓ Authentication strengths
✓ Enterprise app registrations
✓ Entra ID settings
✓ SharePoint settings (Graph + SpoTenant)
✓ Sensitivity labels & DLP
✓ Exchange IRM / OME
✓ Teams settings
✓ Exchange transport rules
✓ Exchange OWA policies
✓ Consent & app permissions
✓ Custom Security Attributes
✓ Win32 / LOB app definitions
✓ MDE device inventory
Everything in one platform

The full picture

CONFIG365 turns Microsoft 365 configuration management into a repeatable, auditable engineering practice. A shared baseline in Git, approval-gated Gitea Actions pipelines, and a hosted management portal give your team complete control across every tenant — all running in a single container you fully own and control.

GitOps baseline

Every policy, group, and configuration as a JSON file. Full history, diffs, and rollback via Git.

Multi-tenant pipelines

One baseline deploys to unlimited tenants. Approval gates and WhatIf previews before any change lands.

Single OCI container

The management portal, Gitea, act_runner, and token-api all ship in one container — deploy to Azure Container Apps, Azure App Service, or any Docker host.

Management portal

Deploy apps, manage groups, inspect drift, and trigger pipelines from a purpose-built web UI.

Nightly automation

Group rebalancing, Exchange fonts, GAL visibility, and device renaming — scheduled and on-demand.

Safe by design

Per-tenant exclusions, resource protection markers, and a two-confirmation remove workflow prevent accidental changes.