-
Create a client inside Keycloak which will be used for authenticating the user using Sunrise.
-
Copy
.env.distto.env.localand configure the local cloud and identity values. Never commit.env.local. -
Start the development server:
pnpm dev -p 9990
-
Navigate to the following URL. You will be redirected to login with Keycloak:
Sunrise runs the noVNC client in the browser and connects directly to
nova-novncproxy. The browser therefore sends the Sunrise dashboard hostname
as the WebSocket origin. Add every public Sunrise hostname to Nova's
[console] allowed_origins
list in each region:
[console]
allowed_origins = sunrise.example.comUse hostnames only, without a scheme, port, path, or wildcard. Separate multiple
hostnames with commas. Local development on this repository's default URL needs
localhost. Reconfigure or restart nova-novncproxy after changing the list.
When TLS terminates in front of Nova, the proxy must also preserve WebSocket
upgrades and set X-Forwarded-Proto to the browser-facing scheme.
The Compose stack builds the Next.js standalone runtime for the local host architecture and runs it as a non-root user with a read-only root filesystem. It also starts an internal Redis-compatible service and selects the server-side session backend, so the same image can be exercised with the persistence model intended for multiple Sunrise replicas. The local stack defaults to Valkey while all Sunrise configuration uses the implementation-neutral Redis protocol vocabulary.
Next.js writes ephemeral image/data cache entries under .next/cache and
promotes immutable prerender seeds into .next/server/route-cache at runtime.
Compose mounts both paths as bounded tmpfs volumes. Kubernetes deployments that
enable readOnlyRootFilesystem must likewise mount writable pod-local
emptyDir volumes at /app/.next/cache and
/app/.next/server/route-cache; neither directory contains shared session
state and each replica may maintain it independently.
docker compose build sunrise
docker compose up -d
docker compose ps
docker compose logs -f sunriseCompose mounts the public development-only Server Actions key from
deploy/local/next-server-actions-encryption-key while building the image. It
exists only to make separate local builds compatible during rollout exercises;
never use it for a published image.
A publishable image must provide an immutable deployment ID and a private, stable Server Actions encryption key. The key is consumed through a BuildKit secret, never an image build argument or runtime environment variable:
docker build \
--secret id=next_server_actions_encryption_key,src=/secure/path/next-server-actions-key \
--build-arg SUNRISE_SERVER_ACTIONS_REVISION=production-v1 \
--build-arg SUNRISE_DEPLOYMENT_ID="$(git rev-parse HEAD)" \
--build-arg SUNRISE_IMAGE_VERSION=0.1.0 \
--tag registry.example.com/sunrise:0.1.0 .The key file must contain canonical base64 for a 16, 24, or 32-byte key. Reuse
the same key for image revisions that can overlap during a rolling deployment.
SUNRISE_SERVER_ACTIONS_REVISION is a non-secret revision or fingerprint used
only to invalidate the builder cache; change it whenever the secret changes.
Production automation should derive it from a cryptographic hash of the key or
advance an operator-managed revision. Never put the key itself in a build
argument.
The deployment ID is compiled into Next.js, retained in the final image, and
reported by /healthz; overriding it with a different runtime value causes
startup validation to fail.
The Image GitHub Actions workflow builds ghcr.io/vexxhost/sunrise for
linux/amd64 and linux/arm64. Pull requests build without publishing. Pushes
to main publish main and immutable sha-<commit> tags; release tags matching
v* publish the matching release tag and the immutable commit tag. The workflow
does not publish a floating latest tag. Published manifests include OCI
metadata, an SBOM, and maximum-mode provenance.
The workflow uses Docker Buildx and QEMU on GitHub-hosted runners to produce the
multi-platform image. BuildKit layers are stored in the GitHub Actions cache to
reduce repeated native and emulated build work; publishing requires only the
repository's standard GITHUB_TOKEN package permission.
Published builds require the
SUNRISE_SERVER_ACTIONS_ENCRYPTION_KEY Actions secret. Generate canonical
base64 once and retain the same value while image revisions may overlap:
openssl rand -base64 32 | tr -d '\n'Pull requests use a disposable build-only key because their images are never
published. The workflow derives the non-secret cache revision from a SHA-256
fingerprint and passes the key itself only through a BuildKit secret mount.
When deploying, prefer the manifest digest or immutable sha-* tag over the
mutable main tag.
Override the implementation image without changing Sunrise configuration:
SUNRISE_REDIS_IMAGE=redis:8.10.1-alpine docker compose --profile replica up -d --force-recreate redis sunrise sunrise-replicaUnset SUNRISE_REDIS_IMAGE and recreate the same services to return to the
default Valkey image. The Compose service and hostname remain redis in both
cases.
Sunrise currently uses only the shared Redis OSS 7.2 command and RESP surface, which Valkey supports with ordinary Redis clients. Keep implementation-specific modules and newer vendor-only commands out of the session layer if this switchability is required. The local data directory is ephemeral by design; do not reuse Redis 7.4+ or Redis 8 persistence files with Valkey during a production migration.
The default local topology is intentionally plain and unauthenticated. The same Compose setup also provides three production-oriented connection exercises:
| Mode | Command |
|---|---|
| Password authentication | SUNRISE_REDIS_PASSWORD='local-test-password' docker compose --profile replica up -d --force-recreate redis sunrise sunrise-replica |
| TLS | docker compose -f compose.yaml -f compose.redis-tls.yaml --profile replica up -d --force-recreate redis sunrise sunrise-replica |
| Password authentication and TLS | SUNRISE_REDIS_PASSWORD='local-test-password' docker compose -f compose.yaml -f compose.redis-tls.yaml --profile replica up -d --force-recreate redis sunrise sunrise-replica |
The TLS override creates a disposable local CA and server certificate in a named volume. Remove that test material with:
docker compose -f compose.yaml -f compose.redis-tls.yaml --profile replica down -vProduction should provide SUNRISE_REDIS_URL=rediss://..., optional
SUNRISE_REDIS_USERNAME, required SUNRISE_REDIS_PASSWORD, and an optional
SUNRISE_REDIS_CA_FILE mounted from deployment secrets when private PKI is in
use. Do not reuse the Compose-generated CA. Authentication alone does not
encrypt traffic, while TLS without authentication does not authorize clients;
use both, network isolation, and a narrowly scoped ACL for production.
For private Keycloak or OpenStack PKI, mount a PEM CA bundle and set
NODE_EXTRA_CA_CERTS to its in-container path. Do not deploy
NODE_TLS_REJECT_UNAUTHORIZED=0; Sunrise reports it as an explicit startup
warning because it disables certificate validation for the whole Node.js
process.
Sunrise is available at http://localhost:9990.
/healthz checks only the application process; /readyz checks Redis when
server-side sessions require it. The readiness probe performs short-lived Lua
writes across the session and refresh namespaces and samples Redis TIME, so
read-only endpoints and ACLs missing required data or shared-clock commands
remove the replica from service instead of accepting traffic it cannot handle.
Run the repeatable local probe baseline after the stack becomes healthy:
pnpm measure:compose
docker image inspect sunrise:local --format '{{.Size}} bytes'
docker stats --no-stream sunrise-local-sunrise-1 sunrise-local-redis-1Start a second independent Sunrise process against the same Redis sessions with:
docker compose --profile replica up -d sunrise-replicaReplica 2 is available at http://localhost:9991. Browser cookies are scoped
to localhost, not its port, so switching between ports exercises the same
opaque session through two processes without load-balancer affinity. Compose
explicitly allows this secondary origin as a trusted same-host dashboard
origin; production remains restricted to SUNRISE_DASHBOARD_URL unless
additional exact origins are configured.
Stop and remove the local stack with docker compose down. The Compose Redis
data directory is deliberately ephemeral: it models a disposable local QA
dependency, not a production data policy. Production deployments must provide
an authenticated, highly available Redis endpoint (preferably rediss://),
shared SUNRISE_SESSION_SECRET, stable deployment namespace, and one immutable
SUNRISE_DEPLOYMENT_ID embedded by the image build pipeline per image build.
It is not deployment-time configuration.
Every server validates its static deployment configuration before accepting requests. Invalid dashboard or identity URLs, missing or short session secrets, malformed federation providers, unsupported service settings, unreadable CA files, invalid Redis settings, and image/runtime deployment-ID skew stop the process. Deliberately weak but locally useful settings such as plain Redis, HTTP dashboard URLs, cookie sessions, and disabled TLS verification produce sanitized startup warnings without logging credentials.
Compose gives Sunrise 30 seconds to finish in-flight requests after SIGTERM.
Next.js handles the signal and drains pending work; Kubernetes should use the
same signal with a configurable termination grace period instead of sending an
immediate SIGKILL.
Sunrise currently connects to one stable writable Redis endpoint. That endpoint
may be provided by a managed service, a Kubernetes operator, or a Sentinel
deployment that exposes the elected primary through stable DNS. Do not point
SUNRISE_REDIS_URL at a Sentinel port. Native Sentinel discovery and Redis
Cluster sharding remain separate deployment features; neither is required for
the expected session workload, and neither is enabled by this local Compose
topology.
For production sessions, high availability alone is not the whole contract: the selected Redis service must provide acceptable durability for login, token rotation, and logout-revocation writes. Losing a recent session revocation has security consequences and must be covered by failover testing.
Redis is deliberately limited to security-bearing session state in this iteration. Sunrise's OpenStack requests are non-cacheable, so each replica uses Next.js's local server cache rather than maintaining a custom distributed cache and tag-invalidation protocol. Revisit shared application caching only when a measured cacheable workload justifies that separate consistency boundary.
The Helm chart in deploy/k8s/chart runs multiple read-only, non-root Sunrise
replicas against a shared Redis-compatible session backend. The disposable kind
harness in deploy/k8s/kind uses the running local cluster, imports the required
runtime settings from .env.local into a Kubernetes Secret, installs Reloader
to restart pods when that external Secret changes, and deploys a temporary
password-protected Valkey instance for integration testing.
make -C deploy/k8s/kind sunrise
make -C deploy/k8s/kind sunrise-status
make -C deploy/k8s/kind sunrise-testThe local route is https://sunrise.localtest.me. See the chart documentation for production inputs, security boundaries, and local-image overrides. Production deployments must use an external highly available Redis-compatible service; the kind Valkey manifest is deliberately ephemeral and is not part of the Helm chart.
Cloud-specific kind settings belong in the ignored
deploy/k8s/kind/values.yaml. When that file is absent, the Makefile falls back
to the tracked values.example.yaml; copy the example before customizing a
local environment. The provider list in that file must match
KEYSTONE_FEDERATION_IDENTITY_PROVIDERS in .env.local; the kind harness checks
this before creating or changing cluster resources.
Set SUNRISE_SESSION_BACKEND=cookie to use the single-process development
fallback. That mode is not suitable for horizontally scaled production
replicas because credential rotation and logout state are held by the browser.
Switching an existing deployment from cookie to redis intentionally
invalidates credential-bearing cookie sessions and requires users to sign in
again. This ensures every accepted authenticated session has a server-side,
revocable identity before its credentials are used.