Setup
Requirements
Database server
The application needs a MongoDB database that can be hosted in a:
- MongoDB Cluster managed by Atlas (easiest solution, free tier available)
- MongoDB Replica Set running from release binaries on one or multiple servers
- MongoDB Replica Set running in containers from MongoDB container image (available on DockerHub)
- Kubernetes cluster using MongoDB Community or Enterprise Kubernetes Operator
Once the database is available, grab the connection string for a user with the readWrite role on the application database.
Tip
Double check any network/security restrictions such as MongoDB IP access list as the application needs to access the MongoDB server
The application does not set a connection pool size, so the driver default of 100 applies.
That is fine at ordinary scale, and it does not need a code change to raise: maxPoolSize is a standard connection string option, for example mongodb://<host>/<db>?maxPoolSize=200.
Database indexes
Add the indexes, the unique ones on tf_state and tf_state_lock being what makes a state write and a lock atomic:
db.tf_state.createIndex({"tenant": 1, "name": 1}, {unique: true})
db.tf_state_history.createIndex({"tenant": 1, "name": 1})
db.tf_state_lock.createIndex({"tenant": 1, "name": 1}, {unique: true})
db.user.createIndex({"username": 1}, {unique: true})
db.auth_lockout.createIndex({"username": 1, "remote_address": 1}, {unique: true})
db.auth_lockout.createIndex({"expires_at": 1}, {expireAfterSeconds: 0})
curl -O https://raw.githubusercontent.com/devpro/terraform-backend-mongodb/refs/heads/main/scripts/tfbeadm && chmod +x ./tfbeadm
MONGODB_URI=mongodb://<myserver>:27017/<mydb> ./tfbeadm create-indexes
Warning
mongosh or Docker must be available on the machine running the commands
Upgrading from a version before 1.3.0
Version 1.3.0 renamed two fields, and tfbeadm migrates existing documents.
Both commands are safe to run more than once.
- A lock stores its ID in
lock_idrather than in the document_id, so the same lock ID can be used on two states. A lock still held at the upgrade, a stale one from a crashed run included, cannot be released untilmigrate-lock-idcopies its ID, so run it once the application is upgraded. - State and history documents store
created_atrather thancreatedAt. The application renames the field on every document it writes, andmigrate-created-atrenames it everywhere at once, for example before a query oncreated_at.
MONGODB_URI=mongodb://<myserver>:27017/<mydb> ./tfbeadm migrate-lock-id
MONGODB_URI=mongodb://<myserver>:27017/<mydb> ./tfbeadm migrate-created-at
Configuration
Settings are read from appsettings.json and overridden by environment variables, with __ as the section separator.
| Setting | Environment variable | Default | Purpose |
|---|---|---|---|
DatabaseSettings:ConnectionString |
DatabaseSettings__ConnectionString |
none | MongoDB connection string. |
DatabaseSettings:DatabaseName |
DatabaseSettings__DatabaseName |
tfbackend |
MongoDB database name. |
Features:IsHttpsRedirectionEnabled |
Features__IsHttpsRedirectionEnabled |
true |
Redirects HTTP requests to HTTPS, to disable when TLS terminates at an ingress. |
Features:IsScalarEnabled |
Features__IsScalarEnabled |
false |
Serves the OpenAPI definition and the Scalar web page on /scalar. |
Authentication:CredentialCacheSeconds |
Authentication__CredentialCacheSeconds |
60 |
How long a verified credential skips BCrypt. |
Authentication:MaxFailedAttempts |
Authentication__MaxFailedAttempts |
10 |
Consecutive failures, per username and source address, before that pair is refused. |
Authentication:LockoutSeconds |
Authentication__LockoutSeconds |
300 |
How long a pair stays refused once locked out. |
Network:KnownProxies |
Network__KnownProxies__0, __1, ... |
none | Reverse proxy addresses whose X-Forwarded-For header the application believes. |
Network:KnownNetworks |
Network__KnownNetworks__0, __1, ... |
none | Same, as CIDR ranges. |
Network:TrustAllProxies |
Network__TrustAllProxies |
false |
Believes the forwarded headers of any caller, safe only where the application is reachable through the ingress alone. |
Warning
Behind a reverse proxy, at least one of Network:KnownProxies, Network:KnownNetworks or Network:TrustAllProxies must be set.
Left unset, the application sees the proxy's own address on every request: every caller then shares a single lockout bucket, and every authentication-failure log entry names the proxy instead of the attacker.
The health check is served on /health.
Warning
The application serves plain HTTP, and Terraform sends the password on every request, so TLS must terminate in front of it, at an ingress or a reverse proxy.
skip_cert_verification in a backend block turns that protection off, and is for local tests only.
Installation
Kubernetes
Add the Helm chart repository:
helm repo add devpro https://devpro.github.io/helm-charts
helm repo update
Create a values.yaml file with the configuration, starting from the examples:
webapi:
host: tfbackend.mydomain
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
dotnet:
environment: Development
enableScalar: true
openTelemetry:
enabled: false
mongodb:
enabled: true
auth:
rootPassword: xxx
webapi:
db:
connectionString: mongodb://root:xxx@tfbackend-mongodb:27017/tfbackend_beta?authSource=admin
databaseName: tfbackend_beta
Install and manage the application with Helm, <tag> being one of the published image tags, since the chart's default tag is not published:
helm upgrade --install tfbackend devpro/terraform-backend-mongodb -f values.yaml --set webapi.tag=<tag> \
--create-namespace --namespace tfbackend