Setup wizard
The setup wizard, goiabada-setup, asks you a few questions and writes the configuration for your deployment, with every key and password already generated.
-
Download the wizard for your platform.
Terminal window curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-linux-amd64chmod +x goiabada-setup-linux-amd64Terminal window curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-linux-arm64chmod +x goiabada-setup-linux-arm64Terminal window curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-darwin-arm64chmod +x goiabada-setup-darwin-arm64Terminal window curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-darwin-amd64chmod +x goiabada-setup-darwin-amd64Terminal window curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-windows-amd64.exeEvery release has the same five binaries on the releases page.
-
Run it in the directory where you want the files, and answer the questions. Pressing Enter takes the default shown in brackets.
Terminal window ./goiabada-setup-linux-amd64 -
Check the summary and answer yes to “Generate configuration files?”.
-
Start Goiabada with the command the wizard prints at the end. It also prints both URLs and where to find the admin password.
Deployment types
Section titled “Deployment types”The first question picks one of four deployments. Each writes its own files, in the current directory unless you pass --output.
| Deployment | Use it for | Files it writes |
|---|---|---|
| 1. Local testing | Trying Goiabada on your machine. Plain HTTP on localhost. The Quickstart walks through it. |
docker-compose.yml and docker-compose.override.yml |
| 2. Production with reverse proxy | Docker Compose behind a proxy that serves HTTPS, such as Nginx or Cloudflare. | docker-compose.yml and docker-compose.override.yml |
| 3. Kubernetes cluster | A cluster, behind Envoy Gateway and cert-manager, or behind the Gateway, Ingress controller or routing it already has. See Routing. | goiabada-k8s.yaml and goiabada-secrets.yaml |
| 4. Native binaries | Running the two binaries yourself, without containers. | goiabada.env |
The questions
Section titled “The questions”The wizard asks only the questions that apply to your deployment, in this order, each under its step’s heading.
| Question | Asked for | Default |
|---|---|---|
| Deployment type | Every deployment. See Deployment types. | 1, Local testing (--type has none) |
| Database type | Every deployment. MySQL, PostgreSQL, SQL Server or SQLite. Kubernetes doesn’t offer SQLite. | 1, MySQL (--db has none) |
| Domain names | The auth server’s and the admin console’s URLs, for every deployment but local testing, which uses http://localhost:9090 and http://localhost:9091. On Kubernetes, each URL needs a host of its own. The auth server’s is at most 60 characters, since the first start makes it the issuer. |
https://auth.example.com, then the admin console’s on the same domain, and on Kubernetes on the auth server’s HTTPS port when it names one: https://admin.example.com |
| Kubernetes namespace | Kubernetes | goiabada |
| Namespace creation | Kubernetes. Yes declares the namespace in both files. No is for a namespace that already exists. | Yes |
| Routing | Kubernetes: how traffic reaches the servers. Envoy Gateway, as Gateway and certificates installs it; a Gateway of a class your cluster has; a Gateway someone else runs; an Ingress per host; or none. The Gateway modes and Ingress then ask what they attach to: see Routing. | 1, Envoy Gateway |
| TLS certificates | Kubernetes, with a Gateway of your cluster’s class or Ingresses: a cert-manager ClusterIssuer or Issuer, TLS Secrets you supply, or TLS ended in front, at a load balancer or a CDN. See Where the certificates come from. | 1, a ClusterIssuer |
| Gateway listeners | Kubernetes, with a Gateway of your cluster’s class: the HTTP and HTTPS listeners’ ports. Some implementations take only their proxy’s own ports, such as Traefik’s 8000 and 8443. | 80 and 443 |
| Gateway traffic policy | Kubernetes, with Envoy Gateway. Cluster works behind every load balancer, but Goiabada then sees a node’s address for every client. Local runs Envoy on every node and keeps each client’s own address, behind a load balancer that passes connections through rather than proxying them. | Cluster |
| Network policy | Kubernetes. Yes admits only your routing layer’s namespace to both servers, and the admin console to the auth server, and asks that namespace and the ports your service mesh delivers on, if you run one. It needs a network plugin that enforces NetworkPolicies. | No, envoy-gateway-system with Envoy Gateway, and no mesh ports |
| Replicas | Kubernetes: how many auth server pods run. From two, a node drain keeps sign-ins up. Each pod opens up to 20 database connections, and a rollout runs one more pod. The admin console runs one pod; see High availability. | 2 |
| Reverse proxy | Native binaries. Yes has both servers listen on 127.0.0.1 and trust the proxy’s forwarded headers. No has them listen on every interface and trust no forwarded header, and you set up their HTTPS yourself. |
Yes |
| Rate limiter | Every deployment but local testing, which leaves it off. See why it’s off on Kubernetes. | On, but off on Kubernetes unless Envoy Gateway’s policy is Local |
| Metrics | Kubernetes. None, pod annotations, or a PodMonitor for the Prometheus Operator. See Monitoring. | None |
| Container images | Every deployment but native binaries: the repository both servers’ images are pulled from, such as a mirror in a registry of your own. A release of the wizard asks whether to pin a mirror’s images by digest, and on Kubernetes the wizard asks for an image pull secret. See Images from your own registry. | docker.io/leodip/goiabada, pinned, and no pull secret |
| Labels and annotations | Kubernetes: labels and annotations every object and both pod templates carry, beside the app.kubernetes.io labels, such as an admission policy requires. See Labels and annotations. |
None |
| Admin credentials | Every deployment: the admin’s email and password. | [email protected] for local testing, otherwise admin@ and the auth server’s domain. A generated password. |
| Database connection | Kubernetes and native binaries, with any database but SQLite: host, port, name, user, password, and the connection’s TLS: its mode, and the CA file, client certificate and host name each mode asks for. Then an optional connection test, which connects as the auth server will. If it fails, you can enter them all again. | The database’s usual port and user, the name goiabada, the TLS mode prefer, the system’s roots, no client certificate, and the database host as the name checked |
| Database password | Local testing and production, with any database but SQLite. The wizard runs that database for you, and writes the TLS mode prefer. |
A generated password |
The database connection’s TLS
Section titled “The database connection’s TLS”The wizard writes GOIABADA_DB_TLS_MODE, which decides how the auth server protects its connection to MySQL, PostgreSQL or SQL Server. Where the database sits explains each mode.
- Local testing and production write
prefer. The database runs on the Compose file’s own network, so the connection never leaves the host. - Kubernetes and native binaries ask you to pick one of
disable,prefer,require,verify-caandverify-full. Onlyverify-fullmakes sure the auth server reached your database and nothing in between. Forverify-caandverify-full, the wizard then asks for a CA file: a PEM file of the authorities your database’s certificate chains to. Leave it blank to trust the system’s roots. The wizard refuses a file the auth server would refuse: one it can’t read, or one that holds no certificate. - On PostgreSQL and MySQL, for
require,verify-caandverify-full, the wizard then asks for a client certificate the auth server presents to the database, and its private key. Leave the certificate blank for none; leaving the key blank asks for the certificate again. - For
verify-full, the wizard last asks for the host name the certificate is checked against. Leave it blank to check the database host. - Native binaries write the CA file’s absolute path as
GOIABADA_DB_TLS_CA_FILE, and the client certificate’s and key’s asGOIABADA_DB_TLS_CLIENT_CERT_FILEandGOIABADA_DB_TLS_CLIENT_KEY_FILE, so keep the files where they are. - Kubernetes copies the certificates into a
goiabada-db-caConfigMap, mounts it read-only into the auth server’s pods at/etc/goiabada/db-ca/ca.pem, and names that path asGOIABADA_DB_TLS_CA_FILE. A private key in the same file isn’t copied. A client certificate and its key go into agoiabada-db-client-tlsSecret ingoiabada-secrets.yaml: see The database client certificate.
The wizard warns you whenever the mode it writes checks no certificate.
The rate limiter on Kubernetes
Section titled “The rate limiter on Kubernetes”The auth server’s limits counted by a user or an email apply whether the rate limiter is on or off: wrong one-time codes, wrong passwords per account, and password-reset and registration mails per address among them. The rate limiter adds the limits counted by an IP address.
Under Envoy Gateway’s Cluster policy, Goiabada sees a node’s address for every client. The per-address limits would then count every user who arrives through one node together, and throttle sign-ins on a busy site. That’s why it’s off by default there. Turned on, it also counts every client through a node as one network: 10 wrong passwords block an email’s password sign-ins for 15 minutes for every client through that node. See the rate limits.
Behind any other routing, the wizard can’t tell which address Goiabada sees, so it’s off by default too. Once Goiabada runs, read the ip= of your own request in the auth server’s log. If it’s your own address, turn the rate limiter on.
Images from your own registry
Section titled “Images from your own registry”Both servers’ images are on Docker Hub, docker.io/leodip/goiabada, and on GitHub’s registry, ghcr.io/leodip/goiabada, under the same tags and digests. A release of the wizard pins each image line to the digest the release pushed.
Where the cluster or the host can reach neither, or may pull only from a registry of your own, copy both images to a mirror there, and answer its repository, such as --image-repository=registry.example.com/mirror/goiabada. The mirror needs the release’s tags, authserver-<version> and adminconsole-<version>, and its digests, for the pinned image lines to pull. These keep them:
-
A pull-through cache, which fetches each image from Docker Hub as it’s asked for.
-
A copy of every platform, with
skopeo copy --all,crane copyordocker buildx imagetools create:Terminal window for image in authserver adminconsole; doskopeo copy --all "docker://docker.io/leodip/goiabada:$image-<version>" \"docker://registry.example.com/mirror/goiabada:$image-<version>"done
docker pull, docker tag and docker push copy one platform, under another digest, which a pinned image line can’t be pulled by: the pull fails with not found. Copy the images again with one of the above, or answer No when the wizard asks whether to pin the images, --image-digest=false, so the image lines name the tag alone.
With MySQL, PostgreSQL or SQL Server, a Compose file also runs the database’s own image, such as postgres:18, which --image-repository doesn’t change. Copy it to your registry too, and add an image line naming your copy to the database’s service already in docker-compose.override.yml, mysql-server, postgres-server or mssql-server. It overrides the image docker-compose.yml names, and --keep-secrets leaves it as it is:
services: postgres-server: image: registry.example.com/mirror/postgres:18A registry that asks for credentials needs them where the images are pulled:
-
Docker Compose: sign the host in,
docker login registry.example.com, beforedocker compose pull. -
Kubernetes: nodes that hold the registry’s credentials already, as managed clusters’ nodes do for their cloud’s registry, need nothing more. Otherwise, create a Secret of type
kubernetes.io/dockerconfigjsonin the namespace, and answer its name, such as--image-pull-secret=registry-credentials: both pod specs name it underimagePullSecrets.--docker-serveris the repository’s first part, the registry’s host, with its port when it has one:Terminal window kubectl create secret docker-registry registry-credentials -n goiabada \--docker-server=registry.example.com --docker-username=<user> --docker-password=<password>Until the Secret exists, the pods wait in
ImagePullBackOff, and their events sayno basic auth credentials. The wizard’s last message has the command, with your labels.The pods run as accounts of their own, so pull secrets that a namespace’s
defaultServiceAccount names don’t reach them. In a namespace that already exists, check for them, and answer the one the registry needs:Terminal window kubectl get serviceaccount default -n goiabada -o jsonpath='{.imagePullSecrets}'
Where the secrets go
Section titled “Where the secrets go”The wizard keeps every secret in one file, and the rest of the configuration in another, so you can commit the second one.
- Docker Compose: the secrets are in
docker-compose.override.yml. Docker Compose merges it intodocker-compose.ymlby itself, sodocker compose up -dstarts both. - Kubernetes: the secrets are in
goiabada-secrets.yaml. Apply it before the manifest, so no pod starts with an older copy of the Secrets; both files create the namespace, unless you said it already exists:kubectl apply -f goiabada-secrets.yaml -f goiabada-k8s.yaml. - Native binaries:
goiabada.envis a single file that holds everything, secrets included.
Every file the wizard writes can be read by its owner only. When it writes into a Git working tree, it warns you and prints the line to add to .gitignore. It never edits .gitignore itself.
Running the wizard again where its files already are writes new secrets over them. Never apply the new secrets file to a deployment that already runs: to write its files again, keep its secrets with --keep-secrets, as Run it again on a deployment shows. To reuse a database with new secrets, copy the old values over them first, as Secrets describes.
With --output, the files go into the directory it names. When it names a file instead, the files beside it are named after it: compose.yaml gets compose.override.yaml and compose-setup.yaml, and my-k8s.yaml gets my-k8s-secrets.yaml and my-k8s-setup.yaml.
Passwords
Section titled “Passwords”The wizard never prints a password. The summary says only whether each one was set or generated, and the last message tells you where the admin password is.
When you type a password at a terminal, the wizard doesn’t show it. It refuses an admin password the auth server would refuse to start with, says why and asks again, or stops when you gave it with --admin-password or --admin-password-file. See GOIABADA_ADMIN_PASSWORD for the rule.
Answering with flags
Section titled “Answering with flags”Pass --type and the wizard asks nothing: it takes every answer from the flags and the defaults. That’s useful in scripts and CI.
./goiabada-setup-linux-amd64 --type=kubernetes --db=postgres \ --auth-url=https://auth.example.com \ --db-host=postgres.default.svc --db-password-file=/run/secrets/db-passwordWithout --type or --answers, the wizard asks every question and reads only --output, --no-color and --keep-secrets, and asks rather than reading --overwrite.
Prefer a generated password, or a password file, to --admin-password and --db-password. A password on the command line lands in your shell history and, while the wizard runs, in the process list. A password file can be - to read standard input, and one trailing line break is dropped.
printf '%s' "$DB_PASSWORD" | ./goiabada-setup-linux-amd64 --type=native --db=postgres \ --auth-url=https://auth.example.com --db-host=localhost --db-password-file=-The wizard exits with 0 when it’s done or when you abort it, 1 when it can’t finish, and 2 when it can’t read a flag. It writes nothing until every answer is valid.
Run it again on a deployment
Section titled “Run it again on a deployment”For Kubernetes and Docker Compose, the wizard also writes goiabada-setup.yaml, the answers file: every answer the files were generated from, defaults included, and no secret. Commit it with the manifest or the Compose file.
--answers reads that file instead of asking, and --keep-secrets writes every file but the secrets file, which it leaves as it is, and generates no secret. Together, they write a deployment’s files again, from a new release of the wizard or with a changed answer. Run them from the directory holding the files:
./goiabada-setup-linux-amd64 --answers=goiabada-setup.yaml --keep-secretsThe same release and the same answers write the same files, byte for byte. A flag given beside --answers replaces the answer of the same name, and the answers file written records it:
./goiabada-setup-linux-amd64 --answers=goiabada-setup.yaml --keep-secrets --metrics=annotationsReview what changed, with git diff, before you apply it, as Upgrade Goiabada shows. On Kubernetes, applying the manifest restarts the pods whose configuration changed. Changes you made to the secrets file stay, since nothing writes it. A change you made to the manifest or the Compose file is lost:
- On Kubernetes, keep it in a kustomization over the manifest, as the Internal TLS guide does. Set a server setting the wizard doesn’t ask as an
enventry of its container, as GitOps shows. - On Docker Compose, set it in the override, the secrets file, under the server’s
environment: it wins over the Compose file’s value of the same name.
Each answer is under the name of the flag that gives it, except two:
db-tls-caholds the certificates of--db-tls-ca-filethemselves, so the answers file writes the manifest on any machine. To change them, give--db-tls-ca-fileagain, or--db-tls-ca-file=to return to the system’s roots.db-tls-client-cert: truesays the auth server presents a database client certificate, whose pair is in the secrets file. To add or replace the pair, create its Secret from your terminal and answerdb-tls-client-cert: true. Empty files,--db-tls-client-cert-file= --db-tls-client-key-file=, stop presenting one.
With prompts, --keep-secrets asks every question but the passwords, and writes the answers file from your answers.
| Flag | What it does |
|---|---|
--type |
The deployment: local, production, kubernetes or native. Giving it turns off the questions. |
--db |
The database: mysql, postgres, mssql or sqlite. Required with --type. |
--auth-url |
The auth server’s URL, at most 60 characters. Required with --type, except for local. |
--admin-url |
The admin console’s URL. Default: https://admin. and the auth server’s domain, so https://admin.example.com for https://auth.example.com, and on Kubernetes the auth server’s HTTPS port when it names one. Required when the auth server’s host is an IP address or a single word. |
--namespace |
Kubernetes: the namespace. Default: goiabada. |
--create-namespace |
Kubernetes: true or false. false is for a namespace that already exists, which neither file then declares. Default: true. |
--admin-email |
The first administrator’s email. Default: [email protected] for local, otherwise admin@ and the auth server’s domain. Required when the auth server’s host is an IP address or a single word. |
--admin-password |
The first administrator’s password. Generated when you leave it out. |
--admin-password-file |
Reads the admin password from a file, or from standard input with -. |
--db-host |
Kubernetes and native: the database’s host. Required unless the database is SQLite. |
--db-port |
Kubernetes and native: the database’s port. Default: 3306, 5432 or 1433, for MySQL, PostgreSQL and SQL Server. |
--db-name |
Kubernetes and native: the database’s name. Default: goiabada. |
--db-user |
Kubernetes and native: the database user. Default: root, postgres or sa. |
--db-password |
The database password. Generated when you leave it out. |
--db-password-file |
Reads the database password from a file, or from standard input with -. |
--db-tls-mode |
Kubernetes and native: how the auth server protects its database connection: disable, prefer, require, verify-ca or verify-full. Default: prefer. |
--db-tls-ca-file |
Kubernetes and native, with --db-tls-mode verify-ca or verify-full: the PEM file of the authorities your database’s certificate is checked against. Default: the system’s roots. |
--db-tls-client-cert-file |
Kubernetes and native, with --db-tls-mode require, verify-ca or verify-full: the PEM file of the client certificate the connection presents to the database, with --db-tls-client-key-file beside it. Not for SQL Server. Default: none. |
--db-tls-client-key-file |
Kubernetes and native: the PEM file of the client certificate’s private key, beside --db-tls-client-cert-file. Default: none. |
--db-tls-server-name |
Kubernetes and native, with --db-tls-mode verify-full: the host name the database’s certificate is checked against, when the address you dial isn’t the name certified. Default: the host dialed. |
--skip-db-test |
Skips the database connection test. Without it, a failed test warns and the files are still written. |
--routing |
Kubernetes: envoy-gateway, gateway, existing-gateway, ingress or none. Default: envoy-gateway. See Routing. |
--gateway-class |
Kubernetes, with --routing=gateway: the GatewayClass. Required. |
--gateway-http-port |
Kubernetes, with --routing=gateway: the HTTP listener’s port. Default: 80. |
--gateway-https-port |
Kubernetes, with --routing=gateway: the HTTPS listeners’ port. Default: 443. |
--gateway-name |
Kubernetes, with --routing=existing-gateway: the Gateway the routes attach to. Required. |
--gateway-namespace |
Kubernetes, with --routing=existing-gateway: that Gateway’s namespace. Required. |
--auth-listener |
Kubernetes, with --routing=existing-gateway: its HTTPS listener for the auth server’s host. Required. |
--admin-listener |
Kubernetes, with --routing=existing-gateway: its HTTPS listener for the admin console’s host. Default: --auth-listener’s. |
--ingress-class |
Kubernetes, with --routing=ingress: the IngressClass. Default: the cluster’s default class. |
--tls |
Kubernetes, with --routing=gateway or ingress: cluster-issuer, issuer, secrets or external. Default: cluster-issuer. |
--tls-issuer |
Kubernetes, with --tls=cluster-issuer or issuer: the cert-manager ClusterIssuer or Issuer. Required. |
--auth-tls-secret |
Kubernetes, with --tls=secrets: the TLS Secret holding the auth server’s certificate. Default: goiabada-tls-auth. |
--admin-tls-secret |
Kubernetes, with --tls=secrets: the TLS Secret holding the admin console’s certificate. Default: goiabada-tls-admin. |
--gateway-traffic-policy |
Kubernetes, with --routing=envoy-gateway: cluster or local. Default: cluster. |
--network-policy |
Kubernetes: admits only your routing layer’s namespace and the admin console to the servers, with NetworkPolicies. |
--proxy-namespace |
Kubernetes, with --network-policy: the namespace your routing layer’s proxies run in. Default: envoy-gateway-system with --routing=envoy-gateway, and required otherwise. |
--mesh-ports |
Kubernetes, with --network-policy: the ports your service mesh’s proxies deliver on between meshed pods, separated by commas, admitted from the same sources: 15008 for Istio’s ambient mode, 4143 for Linkerd. Default: none. |
--authserver-replicas |
Kubernetes: how many auth server pods run. Default: 2. |
--metrics |
Kubernetes: none, annotations or podmonitor. Default: none. |
--podmonitor-labels |
Kubernetes, with --metrics=podmonitor: the labels your Prometheus selects PodMonitors by, such as release=kube-prometheus-stack. |
--metrics-namespace |
Kubernetes, with metrics and --network-policy: the namespace your metrics scraper runs in. Default: monitoring. |
--image-repository |
Kubernetes, local and production: the repository both servers’ images are pulled from, such as registry.example.com/mirror/goiabada. Default: docker.io/leodip/goiabada. |
--image-digest |
Kubernetes, local and production: true or false. true pins each image line to the digest its release pushed, beside the tag. false writes the tag alone, for a mirror holding other digests. A wizard built from source writes the tag alone either way. Default: true. |
--image-pull-secret |
Kubernetes: the Secret holding your registry’s credentials, which you create in the namespace. Default: none. |
--labels |
Kubernetes: labels every object and both pod templates carry, key=value separated by commas, such as team=identity,cost-center=1234. Default: none. |
--annotations |
Kubernetes: annotations every object and both pod templates carry, key=value separated by commas. A value can’t hold a comma. Default: none. |
--rate-limiter |
Production, Kubernetes and native: true or false. Default: true, except on Kubernetes, unless with --routing=envoy-gateway and --gateway-traffic-policy=local. |
--local-proxy |
Native: true when a reverse proxy on the same machine forwards to Goiabada. Default: true. |
--output, -o |
Where to write the files: a directory, or the name of the main file. Default: the current directory. |
--no-color |
Turns off colored output. |
--overwrite |
Without prompts: writes over output files that already exist, and the secrets in them. Without it, the wizard stops. |
--answers |
Kubernetes, local and production: reads the answers from an answers file instead of asking. A flag given beside it replaces the answer of its name. |
--keep-secrets |
Kubernetes, local and production: writes every file but the secrets file, which it leaves as it is, and generates no secret. |
--version, -v |
Prints the wizard’s version. |