# Install

Use this guide to install Zuplo Self-Hosted in a single cluster with automatic
certificates from cert-manager. Before you begin, complete the
[requirements](./requirements.md).

## Set environment variables

Set these variables in the shell that you use for the installation:

```bash
# From the Zuplo portal. See Requirements for instructions.
export ZUPLO_ACCOUNT_NAME='acme-corp'

# Provided by Zuplo during onboarding.
export ZUPLO_CHART_VERSION='<chart version from onboarding>'
export ZUPLO_REGISTRY_KEY='<base64 credential from onboarding>'

# Yours.
export ZUPLO_SUBDOMAIN='api.example.com'
export ZUPLO_MANAGEMENT_HOSTNAME='zuplo-admin.example.com'
export ZUPLO_BUILD_REGISTRY='us-docker.pkg.dev/acme-corp/zuplo-gateways'
export ACME_EMAIL='platform@example.com'
```

## Authenticate to the Zuplo registry

The chart is an OCI artifact in Zuplo's registry. The same credential pulls the
chart and the component images.

```bash
printf '%s' "$ZUPLO_REGISTRY_KEY" |
  helm registry login us-docker.pkg.dev -u _json_key_base64 --password-stdin
```

```
Login Succeeded
```

## Create the values file

Create `zuplo-values.yaml` and commit it to your infrastructure repository. Pass
this file to Helm during every upgrade.

```yaml title="zuplo-values.yaml"
account:
  # Your Zuplo account name.
  name: acme-corp

deployments:
  # Parent domain for your APIs. Needs wildcard DNS:
  #   *.api.example.com -> your ingress address
  subdomain: api.example.com

managementApi:
  # Where deployments are pushed IN. Must be reachable by Zuplo and your CI/CD.
  hostname: zuplo-admin.example.com
  dedicatedIngress:
    enabled: false

builder:
  # Where the in-cluster builder pushes built gateway images.
  provider: docker
  registry: us-docker.pkg.dev/acme-corp/zuplo-gateways
  secretName: builder-secret

cert-manager:
  # Issues and renews one certificate per deployment hostname. Leaving this on
  # is what keeps a wildcard certificate out of the picture.
  enabled: true
  acme:
    # Required when cert-manager.enabled is true. The certificate authority
    # sends expiration warnings to this address.
    email: platform@example.com
```

## Create the credentials file

The chart creates Kubernetes Secrets from Helm values. Put credentials in a
separate values file so that you don't commit them with the rest of the
configuration.

```bash
cat > zuplo-secrets.yaml <<EOF
zuploImageRegistry:
  registry: us-docker.pkg.dev
  username: _json_key_base64
  password: ${ZUPLO_REGISTRY_KEY}

builder:
  username: _json_key_base64
  password: ${ZUPLO_REGISTRY_KEY}
EOF
```

:::danger{title="Treat this file as a secret"}

The chart doesn't support `existingSecret`. It stores these values in Kubernetes
Secrets and in the Helm release history. Don't commit this file as plaintext.
Encrypt it with SOPS or Sealed Secrets, or generate it in CI from a secret
store. Add it to `.gitignore` with your kubeconfig.

:::

## Install the chart

Install the pinned chart version:

```bash
helm install zuplo \
  oci://us-docker.pkg.dev/zuplo-customers/self-hosted/helm-charts/zuplo \
  --version "$ZUPLO_CHART_VERSION" \
  --namespace zuplo --create-namespace \
  -f zuplo-values.yaml \
  -f zuplo-secrets.yaml
```

```
Pulled: us-docker.pkg.dev/zuplo-customers/self-hosted/helm-charts/zuplo:<version>
NAME: zuplo
LAST DEPLOYED: ...
NAMESPACE: zuplo
STATUS: deployed
REVISION: 1
```

The installation takes one to two minutes and creates resources in two
namespaces:

- `zuplo` is the release namespace created by `--create-namespace`. It contains
  gateway deployments and the subcharts.
- `zuplo-system` is created by the chart and contains the Zuplo management
  plane.

:::tip{title="Always pass `--version`"}

Without `--version`, Helm selects the most recent chart available at
installation time. Record the pinned version with `zuplo-values.yaml` so that
you can review and reproduce upgrades.

:::

## Point DNS to the load balancer

Get the address of the `LoadBalancer` Service created by the chart:

```bash
kubectl get svc zuplo-haproxy-ingress -n zuplo
```

```
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)                      AGE
zuplo-haproxy-ingress   LoadBalancer   10.128.82.11   203.0.113.24     80:31670/TCP,443:31011/TCP   50s
```

Create two DNS A records pointing at `EXTERNAL-IP`, both resolving publicly and
neither behind a TLS-terminating proxy:

| Type | Name                      | Value          |
| ---- | ------------------------- | -------------- |
| `A`  | `*.api.example.com`       | `203.0.113.24` |
| `A`  | `zuplo-admin.example.com` | `203.0.113.24` |

Certificates can't be issued until these records resolve. cert-manager checks
the challenge URL from inside the cluster before it contacts the certificate
authority.

Next, [verify your installation](./verify.md).
