Getting Started with Nebula Pulse
Nebula Pulse is a Docker Swarm management platform that gives you a visual control plane for your container infrastructure. Deploy stacks, monitor services, auto-scale workloads, and integrate with your Git workflows — all from a single interface.
What is Nebula Pulse?
At its core, Nebula Pulse connects to the Docker Swarm manager on your host and exposes a rich UI and API for managing your cluster. A lightweight Nebula Agent runs as a global service on every node, collecting real-time CPU and memory metrics that the platform uses for autoscaling and observability.
🆓 Free / Community
- Deploy & destroy stacks
- View logs & service state
- Manual scaling
- Up to 5 users
- Audit logging
⚡ Pro
- Real-time CPU & memory metrics
- CPU-based autoscaling
- Multi-node management via SSH
- Node label management
- Backup & restore
- Full RBAC, unlimited users
🏢 Enterprise
- AI Assistant (YAML generation)
- GitOps auto-deploy
- SSO (Google, Azure AD, LDAP)
- Full REST API access
- Audit & compliance exports
Architecture Overview
Nebula Pulse is designed to run on a single manager node or across a multi-node swarm. The following services make up the platform:
| Service | Purpose | Required |
|---|---|---|
nebula_app | Node.js API + backend logic | Yes |
nebula_web | Nginx serving the frontend SPA | Yes |
nebula_db | PostgreSQL — users, audit logs, GitOps config | Yes |
nebula_agent | Rust telemetry agent (global service on all nodes) | Pro+ |
ai_sidecar | Python RAG assistant + Chroma vector DB | Enterprise |
ai_ollama | Local LLM inference (Ollama) | Enterprise |
Installation
Run the automated installer on your Docker Swarm manager node:
curl -fsSL https://get.nebula-pulse.io/install.sh | sudo bash
The installer will:
- Initialize Docker Swarm (if not already active)
- Generate a random admin password and JWT secret
- Deploy the full Nebula Pulse stack
- Print the access URL and admin credentials
First Login
- Open the URL printed by the installer (default port
80) - Sign in with username
adminand the generated password - You will be prompted to change your password on first login
- After changing your password, the default credential is cleared from the environment for security
Security note: After you change your password, Nebula removes the ADMIN_PASSWORD environment variable from the Docker Swarm service spec so it cannot be reinjected on restart.
User Roles
| Role | Capabilities |
|---|---|
| Admin | Full access — users, nodes, stacks, audit logs, license management |
| DevOps | Deploy/destroy stacks, scale services, manage nodes |
| Developer | View stacks and logs, trigger GitOps syncs |
| User | Read-only — view service status and metrics |
Deployment Modes
Single Node
All services run on one machine. Ideal for development, staging, or small production workloads. Docker Swarm runs in single-node mode — you can still use the full Nebula Pulse feature set.
Multi-Node Cluster
Add worker nodes from the Swarm Topology tab using the SSH join feature (Pro+). The Nebula Agent deploys automatically to every node, enabling per-node metrics and cross-node visibility.
Air-Gapped / Offline
An offline installer package is available. License activation works via file upload — no external network access required.
Upgrades
Nebula Pulse ships an upgrade.sh script that updates an existing installation to a new version without data loss. It backs up your installation, deploys new application files, merges any new environment variables, and redeploys the Swarm stack as a rolling update — all in a single command.
Before You Upgrade
Confirm the following before running the upgrade:
- You are running the command as
root(or withsudo). - An existing Nebula installation exists at
/opt/nebula(the script checks for/opt/nebula/.env). - Docker is running and Docker Swarm is active.
- The upgrade package directory (containing the new
VERSIONfile and application files) is accessible on the host.
The upgrade script displays your current installed version and the new package version before proceeding, and prompts for confirmation. Use --yes to skip the prompt for automated upgrades.
Running the Upgrade
Extract the new package on the host, then run:
cd /path/to/nebula-pulse-X.Y.Z
sudo ./upgrade.sh
# Or skip the confirmation prompt:
sudo ./upgrade.sh --yes
The script runs 7 steps:
- Pre-upgrade backup — saves
.env,license.key,nginx.conf,stacks/, and a Postgres dump to/opt/nebula_backups/pre_upgrade_<version>_<timestamp>.tar.gz. - Stash preserved data — copies credentials, stacks, certs, and AI documents to a temp directory.
- Deploy new application files — copies backend, frontend, compose files, and agent binaries from the package into
/opt/nebula/. - Restore preserved data — writes the stashed
.envback (your credentials are never overwritten). Merges any new environment variables added in this release — existing values are never touched. - Update Docker configs & images — recreates the
nebula_agent_jsandnebula_package_jsonDocker configs, loads the new agent image, and rebuilds the AI sidecar if present. - Rolling redeploy — runs
docker stack deployto apply the updated images and configs with a rolling update (no downtime for replicated services). - Health check — waits 20 seconds, then verifies all Swarm services report the correct replica count.
Database schema migrations (new tables, columns) are applied automatically on the first login after the upgrade via the initDB() function.
What's Preserved
The following are always preserved across an upgrade — they are never overwritten by new package files:
| Item | Location | Notes |
|---|---|---|
| Environment & credentials | /opt/nebula/.env | JWT secret, admin password, DB credentials, SMTP config, etc. |
| License key | /opt/nebula/license.key | Restored verbatim if present |
| TLS certificates | /opt/nebula/certs/ | Custom certs restored from stash |
| Stack configs | /opt/nebula/stacks/ | All user-created stack YAML files and config files |
| AI documents | /opt/nebula/ai/documents/ | User-uploaded RAG knowledge base documents |
| Database data | Docker volume nebula_db_data | Postgres data volume is never touched — only a dump is taken for reference |
New environment variables
Each release may introduce new env vars. The upgrade script automatically appends any missing variables to your .env with safe default values — it never modifies variables that already exist. Review the appended values after upgrading and update them as needed.
Rollback
If something goes wrong, the pre-upgrade backup at /opt/nebula_backups/ contains everything needed to restore your previous state:
# List available backups
ls /opt/nebula_backups/
# Extract the backup you want to restore
cd /opt/nebula_backups
tar -xzf pre_upgrade_4.2.0_20260430_120000.tar.gz
# Restore .env and redeploy the previous version's compose
cp pre_upgrade_4.2.0_20260430_120000/.env /opt/nebula/.env
# Then re-run install or redeploy with the previous package
The Postgres data lives in a Docker named volume (nebula_db_data) and is never deleted by the upgrade script. The pre-upgrade SQL dump in the backup archive is an additional safety net — you can import it with psql if the volume is ever lost.
After any upgrade, check Admin → Audit Log to confirm that the platform started cleanly and that schema migrations completed (look for the login events and any system-init entries).
Features & Tiers
Nebula Pulse ships with three licensing tiers. All tiers include core stack management and audit logging. Advanced metrics, autoscaling, GitOps, and AI capabilities are unlocked progressively.
Free / Community
No license key required. The Free tier is fully functional for individuals and small teams getting started with Docker Swarm management.
Included features
- Deploy and destroy stacks from YAML
- Service state monitoring (Running / Degraded / Down)
- Replica count (actual vs desired)
- Node placement visibility
- Manual scaling with +/− buttons
- Container log viewer
- Stack configuration viewer
- Audit logging
- Up to 5 user accounts
Limitations
- No real-time CPU / memory metrics (Docker Swarm API only)
- No autoscaling
- No multi-node SSH join/remove
- No backup & restore
- No SSO or identity provider integration
Pro
Unlocks deep metrics from the Nebula Agent running on each node, autoscaling, and multi-node cluster management.
Everything in Free, plus:
Deep Metrics (Nebula Agent)
| Metric | Source | Example |
|---|---|---|
| vCPU % | Kernel cgroups via agent | 45.2% |
| RAM | Memory cgroups via agent | 256 MiB |
Autoscaling Labels
deploy:
labels:
nebula.scale.cpu: "70" # Scale up when CPU exceeds 70%
nebula.scale.min: "2" # Always keep at least 2 replicas
nebula.scale.max: "10" # Never exceed 10 replicas
- Node management — add nodes via SSH, drain and remove nodes
- Node label management for placement constraints
- File browser for container filesystem access
- One-click backup download (ZIP: stacks, configs, license)
- Full Role-Based Access Control (RBAC) with unlimited users
- SSO — Google Workspace, Azure AD / Entra ID, LDAP / Active Directory via Dex OIDC
Enterprise
Full platform capabilities, including AI-powered assistance, GitOps integration, and priority support.
Everything in Pro, plus:
- AI Assistant — Natural language Docker Compose generation, RAG-powered documentation queries, troubleshooting help
- GitOps — Auto-deploy on git push from GitHub, GitLab, or Bitbucket
- REST API — Full programmatic access for external integrations
- Audit exports — CSV and JSON export for compliance reporting
Scaling Protection
Nebula prevents accidental scaling of stateful services. If a service name matches a known stateful pattern, a warning is shown before scaling proceeds:
| Protected Patterns |
|---|
postgres, mysql, mariadb, mongodb |
redis, elasticsearch, cassandra |
rabbitmq, kafka, zookeeper |
Any service with _db or -db in its name |
License Enforcement
| Days Remaining | UI Indicator | Feature Access |
|---|---|---|
| > 30 days | None | Full |
| 10–30 days | Yellow warning | Full |
| 1–10 days | Orange warning | Full |
| 0–7 days (Grace) | Red banner | Full (grace period) |
| Expired | Lockout banner | Reverts to Free |
Warning thresholds differ by billing period: 30 days for yearly subscriptions, 10 days for monthly.
Stack Management
Nebula Pulse manages Docker Swarm stacks with automatic directory organisation, config injection, network creation, and bind-mount volume setup. Each stack gets its own directory under /opt/nebula/stacks/.
Directory Structure
stacks/
rabbitmq/
docker-compose.yml # Stack YAML
rabbitmq-config # Config file saved to disk
rabbitmq-definitions # Config file saved to disk
nginx/
docker-compose.yml
html/ # Auto-created bind mount directory
kafka/
docker-compose.yml
Legacy flat stack files (stacks/nginx.yml) are automatically migrated to the directory structure on platform startup.
Deploying a Stack
Via the UI
- Click STACKS → + Deploy Stack
- Enter a stack name — alphanumeric, dashes, and underscores only. Names cannot start with
nebula. - Paste or type your Docker Compose YAML. Enterprise users can use AI Autocraft to generate YAML from a plain-English description.
- If the YAML references Docker configs, input fields appear automatically for each config file.
- Click EXECUTE to deploy.
What happens during deployment
- Config creation — Docker configs are saved to disk and registered in Swarm
- Network creation — External overlay networks are pre-created
- Volume directories — Local bind-mount paths under
/opt/nebula/stacks/are auto-created - YAML processing — The
configs:top-level block is injected withexternal: true - Stack deploy —
docker stack deploy -c <yaml> <name>is executed - Audit log — Success or failure is recorded with full deployment details
Via the API
POST /api/stack/deploy
Content-Type: application/json
{
"name": "mystack",
"yaml": "version: '3.8'\nservices:\n web:\n image: nginx:alpine",
"configs": {
"config-name": "file content here..."
}
}
Destroying a Stack
- Click STACKS → Destroy Stack
- Select the stack from the dropdown
- Confirm the deletion
On destroy, Nebula:
- Removes the stack via
docker stack rm - Waits 3 seconds for services to drain, then removes Docker configs
- Deletes the stack directory from disk
- Writes an audit log entry
Placement Constraints
Pin services to specific nodes using label constraints:
deploy:
placement:
constraints:
- node.labels.rabbitmq == 1
Use Node Management to assign labels to nodes before deploying stacks with placement constraints.
Stack APIs
| Endpoint | Method | Description |
|---|---|---|
/api/stack/deploy | POST | Deploy a stack with optional configs |
/api/stack/destroy | POST | Destroy a stack and clean up configs |
/api/stacks/list | GET | List all deployed stacks |
/api/stack/:name/yaml | GET | Get the YAML for a stack |
/api/stack/:name/configs | GET | Get config references for a stack |
/api/stack/configs/parse | POST | Parse YAML to detect config references |
Troubleshooting
"No suitable node" error
The stack has placement constraints that no nodes satisfy. Use Node Label Management to add the required labels to your nodes.
Config files not appearing in deploy modal
Make sure the YAML has a configs: section at the service level. Config detection is automatic when you type or paste YAML.
Stack failing after config changes
Docker Swarm configs are immutable. When redeploying, Nebula removes the old config and creates a new one. The 3-second drain delay handles in-flight requests.
Bind mount directory not created
Only directories under /opt/nebula/stacks/ are auto-created. For other paths, create the directory manually before deploying.
Autoscaling
Nebula Pulse automatically scales Docker Swarm services horizontally based on CPU utilisation. The autoscaler evaluates each enabled service every 15 seconds and adjusts replicas within the bounds you define.
Autoscaling Labels
Enable autoscaling by adding three labels to the deploy.labels section of your stack YAML:
| Label | Type | Description |
|---|---|---|
nebula.scale.cpu | String (0–100) | CPU % threshold to trigger scale-up |
nebula.scale.min | String (≥ 1) | Minimum replica count (floor) |
nebula.scale.max | String | Maximum replica count (ceiling) |
Labels must be strings — use quotes: "70" not 70. Labels must be in the deploy.labels section, not the top-level service labels.
version: '3.8'
services:
web:
image: nginx:alpine
deploy:
replicas: 1
labels:
nebula.scale.cpu: "70" # Scale up when CPU > 70%
nebula.scale.min: "1" # Keep at least 1 replica
nebula.scale.max: "10" # Never exceed 10 replicas
resources:
limits:
cpus: '0.5'
memory: 256M
Scale Behavior
Scale Up
- Triggers when average CPU exceeds
nebula.scale.cpu - Adds replicas gradually (+1 or +2 at a time)
- Never exceeds
nebula.scale.max - Check interval: 15 seconds
Scale Down
- Triggers when average CPU drops below 50% of the target threshold
- Example: target = 70% → scale down kicks in below 35%
- Removes replicas gradually to avoid disruption
- Never goes below
nebula.scale.min
CPU Limits and Scaling Speed
CPU percentage is calculated relative to the service's resources.limits.cpus value. Lower CPU limits make percentages rise faster under load, which causes the autoscaler to react more quickly.
resources:
limits:
cpus: '0.25' # Lower limit = faster CPU % rise = faster scaling
memory: 128M
reservations:
cpus: '0.1'
memory: 64M
Examples
Stateless Web Application
version: '3.8'
services:
app:
image: nginx:alpine
ports:
- "8080:80"
deploy:
replicas: 2
labels:
nebula.scale.cpu: "60"
nebula.scale.min: "2"
nebula.scale.max: "8"
resources:
limits:
cpus: '0.5'
memory: 256M
restart_policy:
condition: on-failure
networks:
- app_net
networks:
app_net:
driver: overlay
High-Availability API Service
version: '3.8'
services:
api:
image: myapp/api:latest
ports:
- "3000:3000"
deploy:
replicas: 3
labels:
nebula.scale.cpu: "75"
nebula.scale.min: "3"
nebula.scale.max: "15"
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
networks:
- backend
networks:
backend:
driver: overlay
Recommended Thresholds by Service Type
| Service Type | CPU Threshold | Notes |
|---|---|---|
| Web / frontend servers | 60–70% | Low latency sensitive |
| API / microservices | 70–75% | General purpose |
| Databases | 80–85% | Scale conservatively; stateful |
| Background workers | 75–80% | Latency tolerant |
Testing Autoscaling
# Using Apache Bench (100k requests, 50 concurrent, 60 seconds)
ab -n 100000 -c 50 -t 60 http://your-service:port/
# Using hey (simpler syntax)
hey -z 60s -c 50 http://your-service:port/
# Monitor replicas in real time
watch docker service ls
Troubleshooting
Service not scaling
- Confirm labels are in the
deploy.labelssection (not top-levellabels) - Check that
resources.limits.cpusis set on the service - Verify the service is actually under load — check with
docker stats - Ensure
nebula.scale.min<nebula.scale.max
Scaling too aggressively
- Increase the CPU threshold
- Reduce
nebula.scale.max - Increase CPU resource limits so the percentage rises more slowly
Scaling too slowly
- Lower the CPU threshold
- Decrease CPU limits so percentage rises faster
- Increase the initial replica count
GitOps
Nebula Enterprise's GitOps integration automatically deploys updated stack definitions when you push to a configured Git branch. You maintain your infrastructure as code and Nebula handles the deployment.
How GitOps Works
main branchRequirements
- Nebula Enterprise license
- Git repository (GitHub, GitLab, or Bitbucket)
- Repository accessible over HTTPS from your Nebula host
- Nebula accessible from the internet (or from your Git provider's IP range)
Setup
Step 1 — Add Repository in Nebula
- Click GITOPS in the admin panel
- Fill in the repository details: Name, Provider, Repository URL, Branch, and Stack Path
- Click ADD REPOSITORY
- Copy the generated Webhook URL and Secret for the next step
Configuring Webhook Providers
GitHub
- Go to your repo → Settings → Webhooks → Add webhook
- Set Payload URL to the Nebula webhook URL
- Set Content type to
application/json - Paste the Nebula-generated Secret
- Select Just the push event and click Add webhook
GitLab
- Go to your repo → Settings → Webhooks
- Paste the webhook URL and Secret token from Nebula
- Check Push events and click Add webhook
Repository Structure
Organise your repository with stack files under the configured Stack Path (default: /stacks):
/stacks
├── frontend.yml
├── backend.yml
├── database.yml
└── monitoring.yml
# Or by environment:
/stacks
├── production/
│ ├── app.yml
│ └── db.yml
└── staging/
├── app.yml
└── db.yml
Best Practices
Use environment branches
main → Production deployments
staging → Staging environment
develop → Development testing
Configure a separate Nebula GitOps repository entry for each branch.
Pin image versions
services:
api:
image: myorg/api:v1.2.3 # ✅ Specific version
# NOT: image: myorg/api:latest # ❌ Unpredictable
Use Docker secrets for sensitive values
services:
app:
secrets:
- db_password
secrets:
db_password:
external: true # Created separately, never committed to Git
Validate before pushing
# Validate locally before pushing
docker-compose -f stacks/app.yml config
# GitHub Actions validation workflow
# .github/workflows/validate.yml
name: Validate Stacks
on:
push:
paths: ['stacks/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate all stack files
run: |
for f in stacks/*.yml; do
docker-compose -f "$f" config > /dev/null && echo "✅ $f"
done
Troubleshooting
Webhook not triggering
- Verify the webhook URL is correct in your Git provider
- Check the secret matches exactly — no trailing spaces
- Ensure Nebula is accessible from the internet
- Review webhook delivery logs in your Git provider
Deployment fails after push
- Check Nebula app logs: click LOGS on the
nebula_appservice - Verify YAML syntax is valid (
docker-compose config) - Ensure container images are accessible from all swarm nodes
- Confirm the stack path in Nebula matches your repository structure
Signature verification failed
- GitHub — Regenerate the webhook secret in Nebula and update it in GitHub
- GitLab — Ensure the
X-Gitlab-Tokenheader matches the Nebula secret exactly
Security Considerations
- All webhooks use cryptographic signature verification (HMAC-SHA256 for GitHub)
- Webhook secrets are generated using cryptographically secure random bytes
- Use HTTPS URLs for repository access; SSH is not yet supported
- For private repositories:
https://[email protected]/org/repo.git - Consider IP allowlisting your webhook endpoint
- Rotate webhook secrets periodically
Node Management
Pro and Enterprise users can manage their Docker Swarm topology directly from Nebula Pulse — adding worker nodes via SSH, assigning placement labels, and draining or removing nodes from the cluster.
Swarm Topology View
The Swarm Topology table shows all nodes in your cluster with their current status, role, IP address, and assigned labels. Each node displays real-time CPU and memory usage collected by the Nebula Agent.
Labels & Placement Constraints
Docker Swarm node labels are key-value pairs that control which nodes run specific services. Nebula provides a UI for managing labels without touching the CLI.
Labels are set on nodes (via Nebula's Node Management UI), while autoscaling labels are set on services (in your stack YAML's deploy.labels section).
Example placement constraint
deploy:
placement:
constraints:
- node.labels.rabbitmq == 1 # Only run on nodes with this label
Common label patterns
| Label | Purpose |
|---|---|
rabbitmq=1, rabbitmq=2 | Pin specific RabbitMQ nodes to specific hosts |
postgres=primary | Pin the primary database to a dedicated host |
zone=us-east-1a | Availability zone awareness |
tier=frontend | Group nodes by role |
env=production | Environment isolation |
Label format rules
- Keys and values: alphanumeric with dots (
.), dashes (-), or underscores (_) - Maximum 128 characters per key or value
- Examples:
rabbitmq=1,app.role=database,zone=us-east
Managing Labels via UI
Adding a label (Admin only)
- Go to the Swarm Topology table
- Find the target node and click + TAG in the Tags column
- Type the label in
key=valueformat (e.g.rabbitmq=1) - Press Enter or click ADD — the label is applied immediately
Removing a label (Admin only)
- Find the label badge on the node
- Click the red × on the badge and confirm
Automatic Labels
When a node joins the swarm via Nebula's SSH join feature, it automatically receives an ip_address label set to its IP address.
API Reference
| Endpoint | Method | Description |
|---|---|---|
/api/nodes/:id/labels | POST | Add or update labels on a node |
/api/nodes/:id/labels/:key | DELETE | Remove a label from a node |
/api/nodes/list | GET | List all nodes with current labels |
POST /api/nodes/abc123/labels
Authorization: Bearer <jwt>
Content-Type: application/json
{ "labels": { "rabbitmq": "1", "zone": "us-east" } }
→ { "success": true, "message": "Labels updated" }
Audit Logging
All label operations are recorded in the audit trail:
- NODE_LABEL_ADD — Label added or updated (includes node ID and key/value)
- NODE_LABEL_REMOVE — Label removed (includes node ID and key)
Docker Configs
Nebula Pulse supports Docker Swarm configs — a mechanism for injecting configuration files into containers at deploy time. Applications like RabbitMQ, HAProxy, Nginx, and Kafka often require configuration files that must exist before services start.
How It Works
Automatic config detection
When you paste YAML into the Deploy Stack modal, Nebula automatically parses it for Docker config references. If any service contains a configs: section, the UI shows input fields for each config file — one textarea per config.
Deploy flow with configs
- Open the Deploy Stack modal
- Enter a stack name and paste your YAML
- A purple CONFIG FILES section appears listing each detected config with its target path
- Paste the content for each config file into its textarea
- Click DEPLOY
Behind the scenes
- Config content is saved to disk at
/opt/nebula/stacks/<stackname>/ - Each config is registered in Docker Swarm via
docker config create - The YAML is modified to add a top-level
configs:block withexternal: true - The stack is deployed with
docker stack deploy
On stack destroy
- Nebula reads the stack YAML to identify config references
- Waits 3 seconds for services to drain
- Removes each config with
docker config rm - Deletes the stack directory and config files from disk
RabbitMQ Cluster Example
Stack YAML
version: "3.8"
services:
rabbitmq1:
image: rabbitmq:3.13-management
hostname: rabbitmq1
configs:
- source: rabbitmq-config
target: /etc/rabbitmq/rabbitmq.conf
- source: rabbitmq-definitions
target: /etc/rabbitmq/definitions.json
networks:
- rmq_net
deploy:
placement:
constraints:
- node.labels.rabbitmq == 1
networks:
rmq_net:
driver: overlay
rabbitmq-config content
cluster_formation.peer_discovery_backend = classic_config
cluster_formation.classic_config.nodes.1 = rabbit@rabbitmq1
cluster_formation.classic_config.nodes.2 = rabbit@rabbitmq2
cluster_formation.classic_config.nodes.3 = rabbit@rabbitmq3
loopback_users.guest = false
Redeployment
Docker Swarm configs are immutable. When redeploying a stack that already has configs, Nebula removes the old configs first, then creates new ones with the updated content before redeploying the stack.
API Reference
| Endpoint | Method | Description |
|---|---|---|
/api/stack/configs/parse | POST | Parse YAML to detect config references |
/api/stack/deploy | POST | Deploy stack with optional configs object |
/api/stack/:name/configs | GET | Get configs associated with a deployed stack |
POST /api/stack/configs/parse
Content-Type: application/json
{ "yaml": "version: '3.8'\nservices:\n rabbitmq:\n configs:\n - source: my-config\n target: /etc/app/config.conf" }
→ {
"success": true,
"configs": [{ "name": "my-config", "target": "/etc/app/config.conf" }]
}
Secrets
Nebula Pulse integrates with Docker Swarm's native secrets engine to let you store sensitive values — passwords, API keys, TLS certificates, tokens — in an encrypted store and inject them into containers without ever writing them to environment variables, compose files, or disk.
How It Works
Secrets vs environment variables
Environment variables show up in docker inspect output, process lists (ps e), and container logs. Docker Swarm secrets avoid all of this:
| Property | Env Vars | Docker Secrets |
|---|---|---|
| Encrypted at rest | No | Yes — AES-256-GCM via Raft journal |
| Encrypted in transit | No | Yes — mutual TLS between nodes |
Visible in docker inspect | Yes | No |
| Visible in process list | Yes | No |
| Retrievable after creation | Yes | No (write-only) |
| Mounted as file | No | Yes — /run/secrets/<name> |
| Access-scoped to service | No | Yes — only declared services receive it |
Storage model
Secret values are stored exclusively inside Docker Swarm's encrypted Raft log — Nebula Pulse never writes values to its database or disk. Only metadata (name, description, creator, timestamps) is stored in Nebula's Postgres database. Once created, a secret value cannot be read back through any Nebula API.
Container access
When a service declares a secret in its compose YAML, Docker Swarm mounts it at /run/secrets/<name> as a read-only tmpfs file (permissions 0400, owner root). Only processes inside that specific container can read it — no other service or host process has access.
Example YAML
The following stack deploys a PostgreSQL database and a Node.js API using secrets for the DB password and a third-party API key. This is the YAML you would paste into the Deploy Stack modal.
version: "3.8"
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_DB: appdb
# Read password from secret file, not env var
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db_data:/var/lib/postgresql/data
networks:
- app_net
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
api:
image: myorg/myapp:latest
environment:
DB_HOST: db
DB_USER: appuser
DB_NAME: appdb
# App reads /run/secrets/db_password and /run/secrets/stripe_key at startup
secrets:
- db_password
- source: stripe_key
target: /run/secrets/stripe_key
mode: 0400
networks:
- app_net
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
networks:
app_net:
driver: overlay
volumes:
db_data:
secrets:
db_password:
external: true
stripe_key:
external: true
What happens during deploy
- Nebula detects the
secrets:section in your YAML and shows an amber SECRETS panel in the Deploy modal - For each secret, enter its value in the password input — or leave blank if it already exists in Swarm
- Nebula creates the secrets in Docker Swarm by piping values via stdin (never via shell args or temp files)
- The YAML is updated to mark each secret as
external: true - The stack is deployed with
docker stack deploy - Each container that declares the secret receives it mounted at
/run/secrets/<name>
Secrets Manager
Admins and DevOps users can manage all secrets from the SECRETS button in the top navigation bar.
Permissions
| Role | List & view metadata | Create | Rotate | Delete | Use in deploy |
|---|---|---|---|---|---|
| Admin | Yes | Yes | Yes | Yes | Yes |
| DevOps | Yes | Yes | Yes | Yes | Yes |
| Developer | Yes (names only) | No | No | No | Yes |
| User | No | No | No | No | No |
Status indicators
- ACTIVE — secret exists in Docker Swarm and is available to services
- ORPHAN — secret is recorded in Nebula's database but was removed from Docker Swarm externally (e.g. via CLI). Delete and recreate it to restore.
Creating a secret via UI
- Click SECRETS in the top navigation
- Click + NEW SECRET
- Enter a name (letters, numbers,
_and-, max 64 chars) - Enter the secret value — use SHOW to toggle visibility
- Optionally add a description for team reference
- Click CREATE
Secret Rotation
Docker Swarm secrets are immutable — their value cannot be updated in place. Rotation requires deleting and recreating the secret under the same name. Nebula handles this automatically when you click ROTATE.
What rotation does
- Identifies which running services currently reference the secret
- Removes the existing secret from Docker Swarm
- Creates a new secret with the same name and the new value
- Returns a warning listing affected services
Zero-downtime rotation pattern
- Rotate the secret in Nebula Secrets Manager
- Redeploy each affected stack from the Deploy Stack modal — leave the secret field blank to use the already-rotated value
- Docker Swarm performs a rolling update so at least one replica stays up during the transition
Reading Secrets in Your Application
Secrets are available as files at /run/secrets/<name> inside the container. Read them at application startup — never cache them beyond a single request cycle so rotation takes effect on redeploy.
Node.js
const fs = require('fs');
function readSecret(name) {
const secretPath = `/run/secrets/${name}`;
if (fs.existsSync(secretPath)) {
return fs.readFileSync(secretPath, 'utf8').trim();
}
// Fallback to env var for local development
return process.env[name.toUpperCase()];
}
const dbPassword = readSecret('db_password');
const stripeKey = readSecret('stripe_key');
Python
import os
def read_secret(name: str) -> str:
secret_path = f"/run/secrets/{name}"
if os.path.exists(secret_path):
with open(secret_path) as f:
return f.read().strip()
# Fallback to env var for local development
return os.environ.get(name.upper(), "")
db_password = read_secret("db_password")
stripe_key = read_secret("stripe_key")
PostgreSQL — password file support
PostgreSQL's official image supports reading the password directly from a file via the _FILE env var convention. No custom code needed:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
MySQL, MariaDB, and several other official images support the same _FILE pattern.
API Reference
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/secrets | GET | admin, devops, developer | List all secrets (metadata only — values never returned) |
/api/secrets | POST | admin, devops | Create a new secret |
/api/secrets/:name | PUT | admin, devops | Rotate (update) a secret value |
/api/secrets/:name | DELETE | admin, devops | Delete a secret |
/api/stack/secrets/parse | POST | any | Parse YAML to detect secret references |
/api/stack/deploy | POST | admin, devops, developer | Deploy stack; accepts optional secrets object |
Create a secret
POST /api/secrets
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "db_password",
"value": "s3cur3-p@ssw0rd",
"description": "PostgreSQL app user password"
}
→ {
"success": true,
"name": "db_password",
"docker_secret_id": "abc123xyz"
}
Rotate a secret
PUT /api/secrets/db_password
Authorization: Bearer <token>
Content-Type: application/json
{ "value": "n3w-s3cur3-p@ssw0rd" }
→ {
"success": true,
"name": "db_password",
"docker_secret_id": "def456uvw",
"affected_services": ["myapp_api", "myapp_db"],
"warning": "Services using this secret must be redeployed: myapp_api, myapp_db"
}
Deploy stack with inline secrets
POST /api/stack/deploy
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "myapp",
"yaml": "version: '3.8'\n...",
"secrets": {
"db_password": "s3cur3-p@ssw0rd",
"stripe_key": "sk_live_abc123"
}
}
Storage
Nebula Pulse includes a built-in NFS Storage Manager that lets you register remote NFS servers and automatically provision subdirectory paths whenever a stack is deployed. This eliminates manual volume setup and ensures containers can be rescheduled to any Swarm node without losing data.
Why NFS? Docker Swarm bind mounts are node-local — if a container is rescheduled to a different node, bind-mounted data stays behind. NFS volumes are accessible from every node in the cluster, so containers can move freely without data migration.
How It Works
The Storage feature has two parts: a registry of your NFS servers and an auto-provisioning step that runs during stack deploys.
driver: local volumes with type: nfs driver optionsIf an NFS volume in a compose file cannot be matched to a registered server, Nebula logs a warning and skips provisioning for that volume — the deploy is not blocked.
Registering an NFS Server
Open the Storage Manager from the STORAGE button in the top navigation bar (visible to Admin and DevOps roles only).
- Click STORAGE in the top navigation
- Click + Add NFS Server
- Fill in the Name (internal label), Server (IP or hostname), Export Path (the root NFS export on the server), and optional Mount Options
- Click Save
- Use the TEST button to verify TCP reachability on port 2049 before deploying
| Field | Description | Example |
|---|---|---|
| Name | Unique internal label for this NFS server | nas-01 |
| Server | IP address or hostname of the NFS server | 192.168.1.50 |
| Export Path | The root NFS export path shared by the server | /mnt/pool/nebula |
| Mount Options | NFS mount flags — do not include addr=, that is stored separately | rw,nfsvers=4,soft |
| Description | Optional free-text notes for your team | Main NAS — pool storage |
Do not include addr= in Mount Options. The server IP is stored in the Server field. Nebula inserts it into the NFS mount string automatically at deploy time.
Permissions
| Role | View servers | Add / Edit | Delete | Test connection |
|---|---|---|---|---|
| Admin | Yes | Yes | Yes | Yes |
| DevOps | Yes | Yes | Yes | Yes |
| Developer | No | No | No | No |
| User | No | No | No | No |
Volume YAML
Use the standard Docker Compose driver: local volume with type: nfs driver options. Nebula reads the addr from the o option string and the device path from driver_opts.device to match against your registered NFS servers.
version: "3.8"
services:
nextcloud:
image: nextcloud:28-apache
volumes:
- nc_data:/var/www/html
networks:
- nc_net
deploy:
replicas: 1
networks:
nc_net:
driver: overlay
volumes:
nc_data:
driver: local
driver_opts:
type: nfs
o: "addr=192.168.1.50,rw,nfsvers=4,soft"
device: ":/mnt/pool/nebula/nextcloud/data"
Key fields
| Field | Purpose |
|---|---|
driver: local | Standard Docker volume driver — required for NFS volumes in Swarm |
type: nfs | Tells Docker (and Nebula) this is an NFS volume |
o: "addr=IP,..." | NFS mount options; addr must match a registered NFS server's IP |
device: ":/export/subpath" | Leading colon is required. Path = export root + subpath. Nebula auto-creates the subpath. |
Auto-Provisioning
When you deploy a stack containing NFS volumes, Nebula's deploy pipeline automatically creates the required subpath directories on the NFS share before any containers start.
What happens step-by-step
- Nebula parses your compose YAML and extracts all volumes with
type: nfs - For each volume, it reads the
addrfrom theostring and the full device path - It matches
addragainst your registered NFS servers by the Server field - It computes the subpath = device path relative to the registered export root
- A temporary one-shot Docker service (pinned to the Swarm manager) is created; it mounts the NFS export and runs
mkdir -pfor each subpath - The provisioner service is removed immediately after completion; the temporary volume is removed after 10 seconds
- The main stack is deployed — all required directories are guaranteed to exist
Root-level devices are skipped. If device equals the export root exactly (no subpath), Nebula assumes the directory already exists and skips provisioning. Only subdirectories trigger the auto-provisioner.
Deploy panel
When the YAML editor contains NFS volumes, a green STORAGE panel automatically appears in the Deploy Stack modal showing each volume and its match status:
- ✓ nas-01 — volume matched to a registered NFS server; will be auto-provisioned
- ✗ Not registered — no matching server found; a link to the Storage Manager is shown. The deploy will proceed, but the subpath will not be created.
API Reference
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/storage/nfs | GET | admin, devops | List all registered NFS server configs |
/api/storage/nfs | POST | admin, devops | Register a new NFS server |
/api/storage/nfs/:id | PUT | admin, devops | Update server, export path, mount options, or description |
/api/storage/nfs/:id | DELETE | admin, devops | Remove a registered NFS server |
/api/storage/nfs/:id/test | POST | admin, devops | TCP probe on port 2049 — returns reachability status |
/api/stack/nfs/parse | POST | any | Parse compose YAML; returns list of NFS volumes with addr and device |
Register an NFS server
POST /api/storage/nfs
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "nas-01",
"server": "192.168.1.50",
"export_path": "/mnt/pool/nebula",
"mount_options": "rw,nfsvers=4,soft",
"description": "Main NAS — pool storage"
}
→ {
"id": 1,
"name": "nas-01",
"server": "192.168.1.50",
"export_path": "/mnt/pool/nebula",
"mount_options": "rw,nfsvers=4,soft"
}
Test connection
POST /api/storage/nfs/1/test
Authorization: Bearer <token>
→ { "reachable": true, "latency_ms": 2 }
or
→ { "reachable": false, "error": "ECONNREFUSED" }
Parse NFS volumes from YAML
POST /api/stack/nfs/parse
Authorization: Bearer <token>
Content-Type: application/json
{ "yaml": "version: '3.8'
..." }
→ {
"volumes": [
{ "volName": "nc_data", "addr": "192.168.1.50", "device": "/mnt/pool/nebula/nextcloud/data" },
{ "volName": "nc_db", "addr": "192.168.1.50", "device": "/mnt/pool/nebula/nextcloud/db" }
]
}
Audit Logging
Nebula Pulse records a comprehensive audit trail of all user actions and system events. Audit logs are available to admin users across all license tiers and include automatic 90-day retention with configurable cleanup.
Event Types
Authentication Events
| Action | Description |
|---|---|
LOGIN_SUCCESS | User successfully authenticated |
LOGIN_FAILURE | Failed login attempt (wrong credentials) |
User Management Events
| Action | Description |
|---|---|
USER_CREATE | New user account created |
USER_DELETE | User account deleted |
PASSWORD_CHANGE | User changed their own password |
PASSWORD_RESET | Admin reset a user's password |
Stack & Service Events
| Action | Description |
|---|---|
STACK_DEPLOY | Stack deployed — success or failure both logged |
STACK_DESTROY | Stack destroyed |
SERVICE_SCALE | Service scaled up or down |
SERVICE_RESTART | Service force-restarted |
Node Events
| Action | Description |
|---|---|
NODE_JOIN | New node joined the swarm via SSH |
NODE_REMOVE | Node drained and removed from swarm |
NODE_LABEL_ADD | Label added or updated on a node |
NODE_LABEL_REMOVE | Label removed from a node |
GitOps Events (Enterprise)
| Action | Description |
|---|---|
GITOPS_REPO_ADD | Git repository added for GitOps |
GITOPS_REPO_DELETE | Git repository removed |
GITOPS_SYNC | Manual or webhook-triggered sync |
System Events
| Action | Description |
|---|---|
LICENSE_ACTIVATE | License key activated |
BACKUP_DOWNLOAD | Backup ZIP downloaded |
FILE_UPLOAD | File uploaded to a container |
Log Entry Fields
Every audit log entry contains:
| Field | Description |
|---|---|
timestamp | When the event occurred (ISO 8601) |
username | Who performed the action |
action | Event type code (e.g. STACK_DEPLOY) |
category | Event category (AUTH, STACK, NODE, etc.) |
target_type | What was affected (stack, node, user, etc.) |
target_name | Name of the affected resource |
ip_address | Client IP (forwarded through Nginx proxy) |
user_agent | Browser / client user agent string |
status | success or failure |
details | Additional context as JSON (error messages, etc.) |
session_id | JWT session identifier |
API & Export
Query audit logs
GET /api/audit/logs?page=1&limit=50&category=STACK&status=failure
Authorization: Bearer <admin-jwt>
Available query parameters: page, limit, category, action, username, status, startDate, endDate
Get audit statistics
GET /api/audit/stats
→ Event counts by category, status breakdown, failed logins, active users
Export logs
GET /api/audit/export?format=csv&startDate=2025-01-01&endDate=2025-12-31
→ Returns CSV or JSON file download
Viewing Logs in the UI
- Log in as an admin user
- Click the admin menu dropdown (top right)
- Select Audit Logs
- Use the filters to search by category, action, user, date range, or status
- Click Export to download as CSV or JSON
Retention Policy
- Default retention: 90 days
- Configurable via
AUDIT_RETENTION_DAYSenvironment variable - Automatic daily cleanup of expired entries
- Cleanup runs on a 24-hour interval
Licensing
Nebula Pulse uses JWT-based license keys to gate features across three tiers. The license file is stored at /opt/nebula/license.key and validated on startup and every hour thereafter.
License Tiers
| Tier | License Required | Key Capabilities |
|---|---|---|
| Free | None | Stack management, manual scaling, audit logging, up to 5 users |
| Pro | JWT license key | + Real-time metrics, autoscaling, multi-node management, RBAC, backup |
| Enterprise | JWT license key | + AI assistant, GitOps, full REST API |
Activating a License
Via the UI
- In the Nebula Pulse UI, click ACTIVATE PRO (visible on the Free tier)
- Paste your license key into the input field
- Click ACTIVATE
- The page reloads with the new tier active
License Status API
GET /api/license/status
→ {
"tier": "enterprise",
"billing": "yearly",
"client": "Acme Corp",
"expired": false,
"gracePeriod": false,
"expiresAt": "2027-01-01T00:00:00.000Z",
"daysRemaining": 300,
"features": ["backup", "nodes", "scaling", "rbac", "ai", "sso", "audit", "api"]
}
Expiry & Grace Period
- Licenses are validated on startup and every hour
- On expiry, a 7-day grace period begins — all features remain active
- After the grace period, the system reverts to Free tier
| Days Until Expiry | Warning | Billing Type |
|---|---|---|
| 30 days | Yellow banner | Yearly |
| 10 days | Yellow banner | Monthly |
| 1–10 days | Orange banner | Both |
| 0–7 days (expired) | Red banner — grace period active | Both |
Troubleshooting
"License key has expired"
Generate a new key with a future expiration date and activate it via the UI.
Features not appearing after activation
The license is re-read on startup and every hour. If features still don't appear, restart the nebula_app service to force an immediate re-validation.
Key not working
Ensure the license was generated with the correct signing secret. Both the generator and the server must use the same secret key.
Identity Providers
Nebula Pulse Pro and Enterprise support Single Sign-On (SSO) via LDAP/LDAPS, Azure Active Directory (Entra ID), and Google Workspace. Once configured, users can authenticate with their existing corporate credentials instead of local passwords.
Pro or Enterprise license required. Identity provider configuration is gated behind the SSO feature flag. Activate your Pro or Enterprise license before proceeding.
LDAP / LDAPS
Nebula Pulse can authenticate users against any RFC 4511-compliant LDAP directory, including Active Directory, OpenLDAP, and FreeIPA. Use ldaps:// (port 636) for production deployments to encrypt the bind credential in transit.
Configuration
Add the following block to your Nebula Pulse environment (via .env or Docker Swarm secrets) and restart the service:
LDAP_ENABLED=true
LDAP_URL=ldaps://ldap.example.com:636
LDAP_BIND_DN=cn=svc-nebula,ou=service-accounts,dc=example,dc=com
LDAP_BIND_PASSWORD=supersecret
LDAP_SEARCH_BASE=ou=users,dc=example,dc=com
LDAP_SEARCH_FILTER=(sAMAccountName={{username}})
LDAP_TLS_CA=/run/secrets/ldap-ca.pem # optional — custom CA for LDAPS
LDAP_ATTR_EMAIL=mail
LDAP_ATTR_DISPLAY_NAME=displayName
Configuration Reference
| Variable | Required | Description |
|---|---|---|
LDAP_ENABLED | Yes | Set to true to enable LDAP authentication |
LDAP_URL | Yes | LDAP server URI — use ldaps:// for TLS |
LDAP_BIND_DN | Yes | Distinguished name of the service account used for directory searches |
LDAP_BIND_PASSWORD | Yes | Password for the bind service account |
LDAP_SEARCH_BASE | Yes | Base DN under which users are searched |
LDAP_SEARCH_FILTER | Yes | LDAP filter to locate a user; {{username}} is replaced at runtime |
LDAP_TLS_CA | No | Path to a PEM CA bundle for private/self-signed LDAPS certificates |
LDAP_ATTR_EMAIL | No | LDAP attribute to map to the user email (default: mail) |
LDAP_ATTR_DISPLAY_NAME | No | LDAP attribute to map to the display name (default: displayName) |
How It Works
- User submits their username and password at the Nebula Pulse login page.
- The backend binds to the LDAP server using the service account credentials.
- A search is performed using
LDAP_SEARCH_FILTERto locate the user's DN. - The backend attempts a bind with the located DN and the user-supplied password.
- On success, a JWT session token is issued. The user account is auto-provisioned on first login if it does not already exist locally.
Plain LDAP (port 389) transmits credentials in cleartext. Always use ldaps:// or configure StartTLS in production environments.
Troubleshooting
- Invalid credentials — verify the bind DN and password with
ldapsearch -H ldaps://host -D "<bind_dn>" -W -b "<search_base>" - Certificate errors — supply
LDAP_TLS_CApointing to your internal CA bundle - Users not found — test your search filter directly:
ldapsearch -H ldaps://host -D "<bind_dn>" -W -b "<base>" "(sAMAccountName=testuser)" - Group-based access — extend
LDAP_SEARCH_FILTERto require group membership, e.g.(&(sAMAccountName={{username}})(memberOf=cn=nebula-users,ou=groups,dc=example,dc=com))
Azure Active Directory (Entra ID)
Nebula Pulse integrates with Azure AD using OAuth 2.0 Authorization Code flow with PKCE. Users are redirected to Microsoft's login page and returned with an ID token that Nebula Pulse validates against your tenant.
Step 1 — Register an Application in Azure
- In the Azure portal, go to Azure Active Directory → App registrations → New registration.
- Set the Name (e.g. Nebula Pulse) and select the appropriate Supported account types (typically Accounts in this organizational directory only).
- Under Redirect URI, select Web and enter:
https://<your-nebula-host>/api/auth/azure/callback - Click Register. Note the Application (client) ID and Directory (tenant) ID.
- Go to Certificates & secrets → New client secret. Copy the generated value immediately — it is only shown once.
Step 2 — Configure Nebula Pulse
AZURE_AD_ENABLED=true
AZURE_AD_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_AD_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_AD_CLIENT_SECRET=your-client-secret-value
AZURE_AD_REDIRECT_URI=https://nebula.example.com/api/auth/azure/callback
# Optional: restrict login to specific Azure AD groups
AZURE_AD_ALLOWED_GROUPS=sg-nebula-admins,sg-nebula-users
Configuration Reference
| Variable | Required | Description |
|---|---|---|
AZURE_AD_ENABLED | Yes | Set to true to enable Azure AD SSO |
AZURE_AD_TENANT_ID | Yes | Your Azure AD Directory (tenant) ID |
AZURE_AD_CLIENT_ID | Yes | Application (client) ID from the app registration |
AZURE_AD_CLIENT_SECRET | Yes | Client secret value generated in Azure |
AZURE_AD_REDIRECT_URI | Yes | Must exactly match the redirect URI registered in Azure |
AZURE_AD_ALLOWED_GROUPS | No | Comma-separated Azure AD group names or object IDs to restrict access |
Required API Permissions
In your Azure app registration under API permissions, ensure the following Microsoft Graph Delegated permissions are granted:
openid— required for ID token issuanceprofile— user display name and UPNemail— user email addressGroupMember.Read.All— required only if usingAZURE_AD_ALLOWED_GROUPS
After adding permissions, click Grant admin consent to avoid per-user consent prompts.
Troubleshooting
- AADSTS50011: redirect_uri mismatch — the
AZURE_AD_REDIRECT_URImust exactly match (including trailing slash) what is registered in Azure. - AADSTS700016: application not found — verify
AZURE_AD_TENANT_IDandAZURE_AD_CLIENT_ID. - User not in allowed group — check that the group name or object ID in
AZURE_AD_ALLOWED_GROUPSmatches the Azure AD group exactly. - Token validation failure — ensure the server clock is synchronized (NTP) — JWT validation is time-sensitive.
Google Workspace
Nebula Pulse supports Google OAuth 2.0 for authentication. Users sign in with their Google Workspace (or personal Google) account. You can optionally restrict login to a specific Workspace domain.
Step 1 — Create OAuth Credentials in Google Cloud
- Open the Google Cloud Console and select or create a project.
- Navigate to APIs & Services → OAuth consent screen. Set the User Type to Internal (Workspace org only) or External, fill in the required fields, and save.
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Choose Web application. Under Authorized redirect URIs add:
https://<your-nebula-host>/api/auth/google/callback - Click Create. Download or copy the Client ID and Client secret.
Step 2 — Configure Nebula Pulse
GOOGLE_AUTH_ENABLED=true
GOOGLE_CLIENT_ID=xxxxxxxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI=https://nebula.example.com/api/auth/google/callback
# Optional: restrict login to your Workspace domain
GOOGLE_ALLOWED_DOMAIN=example.com
Configuration Reference
| Variable | Required | Description |
|---|---|---|
GOOGLE_AUTH_ENABLED | Yes | Set to true to enable Google SSO |
GOOGLE_CLIENT_ID | Yes | OAuth 2.0 Client ID from Google Cloud Console |
GOOGLE_CLIENT_SECRET | Yes | OAuth 2.0 Client Secret |
GOOGLE_REDIRECT_URI | Yes | Must exactly match the authorized redirect URI in Google Cloud |
GOOGLE_ALLOWED_DOMAIN | No | Restrict logins to accounts from this Google Workspace domain (e.g. example.com) |
Scopes Requested
Nebula Pulse requests the following OAuth scopes during authentication:
openid— identity tokenemail— user's email addressprofile— display name and avatar
If you leave GOOGLE_ALLOWED_DOMAIN unset and use an External consent screen, any Google account will be able to attempt login. Always set the domain for production deployments.
Troubleshooting
- redirect_uri_mismatch — the
GOOGLE_REDIRECT_URIvalue must exactly match one of the authorized redirect URIs in Google Cloud Console. - access_denied — if the consent screen is in Testing mode, only test users can authenticate. Publish the app or add the user as a tester.
- Domain not allowed — the authenticated user's email domain does not match
GOOGLE_ALLOWED_DOMAIN. Update the variable or use a different account. - invalid_client — double-check
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET; client secrets are invalidated when regenerated.