Skip to content

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.

  1. Download the wizard for your platform.

    Terminal window
    curl -LO https://github.com/leodip/goiabada/releases/latest/download/goiabada-setup-linux-amd64
    chmod +x goiabada-setup-linux-amd64

    Every release has the same five binaries on the releases page.

  2. 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
  3. Check the summary and answer yes to “Generate configuration files?”.

  4. Start Goiabada with the command the wizard prints at the end. It also prints both URLs and where to find the admin password.

  5. Sign in to the admin console.

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 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 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-ca and verify-full. Only verify-full makes sure the auth server reached your database and nothing in between. For verify-ca and verify-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-ca and verify-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 as GOIABADA_DB_TLS_CLIENT_CERT_FILE and GOIABADA_DB_TLS_CLIENT_KEY_FILE, so keep the files where they are.
  • Kubernetes copies the certificates into a goiabada-db-ca ConfigMap, mounts it read-only into the auth server’s pods at /etc/goiabada/db-ca/ca.pem, and names that path as GOIABADA_DB_TLS_CA_FILE. A private key in the same file isn’t copied. A client certificate and its key go into a goiabada-db-client-tls Secret in goiabada-secrets.yaml: see The database client certificate.

The wizard warns you whenever the mode it writes checks no certificate.

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.

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 copy or docker buildx imagetools create:

    Terminal window
    for image in authserver adminconsole; do
    skopeo 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:18

A registry that asks for credentials needs them where the images are pulled:

  • Docker Compose: sign the host in, docker login registry.example.com, before docker 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/dockerconfigjson in the namespace, and answer its name, such as --image-pull-secret=registry-credentials: both pod specs name it under imagePullSecrets. --docker-server is 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 say no 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 default ServiceAccount 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}'

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 into docker-compose.yml by itself, so docker compose up -d starts 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.env is 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.

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.

Pass --type and the wizard asks nothing: it takes every answer from the flags and the defaults. That’s useful in scripts and CI.

Terminal window
./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-password

Without --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.

Terminal window
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.

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:

Terminal window
./goiabada-setup-linux-amd64 --answers=goiabada-setup.yaml --keep-secrets

The 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:

Terminal window
./goiabada-setup-linux-amd64 --answers=goiabada-setup.yaml --keep-secrets --metrics=annotations

Review 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 env entry 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-ca holds the certificates of --db-tls-ca-file themselves, so the answers file writes the manifest on any machine. To change them, give --db-tls-ca-file again, or --db-tls-ca-file= to return to the system’s roots.
  • db-tls-client-cert: true says 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 answer db-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.