From b103cc93cc520c93588e5ffc38db4e4f7b92d518 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 2 Sep 2026 12:13:34 -0700 Subject: [PATCH 1/5] fix(site): restore manual agent install guidance --- apps/site/src/pages/index.astro | 49 ++++++++----- apps/site/src/styles/landing.css | 120 ++++++++++++++++++++++++------- 2 files changed, 128 insertions(+), 41 deletions(-) diff --git a/apps/site/src/pages/index.astro b/apps/site/src/pages/index.astro index 691b1b7..33012ba 100644 --- a/apps/site/src/pages/index.astro +++ b/apps/site/src/pages/index.astro @@ -78,18 +78,15 @@ When the setup is complete, begin your final response with “Artifact Server is during product development. Publish, review, comment on, version, and share the same work. The built-in MCP server gives agents direct access.

-
- - Run it locally - - - - Deploy for a team - - -
- - - Connect MCP + +
+
+ + Run it locally + + Deploy for a team + +
@@ -340,10 +357,10 @@ When the setup is complete, begin your final response with “Artifact Server is try { await navigator.clipboard.writeText(text); button.dataset.copied = "true"; - button.setAttribute("aria-label", "Artifact Server setup prompt copied"); + button.setAttribute("aria-label", button.dataset.copySuccessLabel ?? "Copied"); window.setTimeout(() => { delete button.dataset.copied; - button.setAttribute("aria-label", "Copy the Artifact Server setup prompt"); + button.setAttribute("aria-label", button.dataset.copyLabel ?? "Copy"); }, 1600); } catch { // Clipboard unavailable (insecure context or permission denied). diff --git a/apps/site/src/styles/landing.css b/apps/site/src/styles/landing.css index 5537f66..1459885 100644 --- a/apps/site/src/styles/landing.css +++ b/apps/site/src/styles/landing.css @@ -233,14 +233,13 @@ } .landing-agent-setup { - display: flex; + display: grid; max-width: 36rem; margin-top: 1.25rem; - gap: 0.75rem; + gap: 0.5rem; } -.landing-copy-prompt, -.landing-agent-setup__docs { +.landing-copy-prompt { display: flex; min-height: 3.25rem; align-items: center; @@ -258,7 +257,6 @@ .landing-copy-prompt { min-width: 0; - flex: 1 1 auto; gap: 0.875rem; padding: 0.75rem 0.875rem; font: inherit; @@ -286,7 +284,8 @@ } .landing-copy-prompt__label { - min-width: 5.75rem; + min-width: 0; + flex: 1; font-size: 0.875rem; font-weight: 650; text-align: left; @@ -310,8 +309,7 @@ color: var(--nb-success); } -.landing-copy-prompt__icon, -.landing-agent-setup__docs svg { +.landing-copy-prompt__icon { display: grid; width: 1rem; height: 1rem; @@ -319,26 +317,87 @@ place-items: center; } -.landing-agent-setup__docs { - flex: none; - gap: 0.5rem; - padding: 0.75rem 1rem; - font-size: 0.8125rem; - font-weight: 650; +.landing-manual-command { + display: grid; + min-height: 2.75rem; + grid-template-columns: minmax(0, 1fr) auto 2rem; + align-items: center; + gap: 0.75rem; + padding: 0.375rem 0.375rem 0.375rem 0.875rem; + border: 1px solid var(--nb-border); + background: var(--nb-card); + font-family: var(--nb-font-mono); + font-size: 0.6875rem; +} + +.landing-manual-command code { + min-width: 0; + overflow: hidden; + color: var(--nb-foreground); + text-overflow: ellipsis; white-space: nowrap; } +.landing-manual-command a { + color: var(--nb-muted-foreground); + text-decoration: none; + white-space: nowrap; + transition: color 150ms ease; +} + +.landing-copy-icon-button { + display: grid; + width: 2rem; + height: 2rem; + place-items: center; + border: 1px solid var(--nb-border); + background: transparent; + color: var(--nb-muted-foreground); + cursor: pointer; + transition: + border-color 150ms ease, + color 150ms ease, + transform 150ms ease; +} + +.landing-copy-icon-button svg { + width: 0.875rem; + height: 0.875rem; +} + +.landing-copy-icon-button svg:last-child, +.landing-copy-icon-button[data-copied] svg:first-child { + display: none; +} + +.landing-copy-icon-button[data-copied] { + border-color: color-mix(in oklch, var(--nb-success) 70%, var(--nb-border)); + color: var(--nb-success); +} + +.landing-copy-icon-button[data-copied] svg:last-child { + display: block; +} + .landing-copy-prompt:active, -.landing-agent-setup__docs:active { +.landing-copy-icon-button:active { transform: scale(0.96); } @media (hover: hover) { - .landing-copy-prompt:hover, - .landing-agent-setup__docs:hover { + .landing-copy-prompt:hover { border-color: var(--nb-foreground); background: var(--nb-muted); } + + .landing-manual-command a:hover, + .landing-copy-icon-button:hover { + color: var(--nb-foreground); + } + + .landing-copy-icon-button:hover { + border-color: var(--nb-foreground); + } } /* Hero: the full-screen review — artifact canvas plus the open comment panel. */ @@ -1256,18 +1315,20 @@ } .landing-agent-setup { - align-items: stretch; - flex-direction: column; + width: 100%; } .landing-copy-prompt { - justify-content: flex-start; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + justify-items: start; gap: 0.625rem; padding-inline: 0.625rem; } .landing-agent-setup__marks { - flex: 1 1 auto; + grid-column: 1; + grid-row: 1; gap: 0.25rem; } @@ -1278,15 +1339,28 @@ } .landing-copy-prompt__label { + grid-column: 1 / -1; + grid-row: 2; min-width: auto; font-size: 0.8125rem; } .landing-copy-prompt__icon { + grid-column: 2; + grid-row: 1; width: 0.875rem; height: 0.875rem; } + .landing-manual-command { + grid-template-columns: minmax(0, 1fr) 2rem; + } + + .landing-manual-command a { + grid-column: 1 / -1; + grid-row: 2; + } + .artifact-window { height: 30rem; box-shadow: 0.75rem 0.75rem 0 color-mix(in oklch, var(--nb-primary) 12%, transparent); @@ -1300,10 +1374,6 @@ grid-template-columns: 1fr; } - .landing-agent-setup__docs { - align-self: flex-start; - } - .artifact-canvas { display: none; } From b8ba6c602fa8099e40a56c0de62fdde2985e6383 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 2 Sep 2026 12:16:21 -0700 Subject: [PATCH 2/5] fix(site): link install prompt to markdown docs --- apps/site/scripts/verify-agent-surfaces.mjs | 21 +++++++++++++++++++++ apps/site/src/pages/index.astro | 10 +++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/site/scripts/verify-agent-surfaces.mjs b/apps/site/scripts/verify-agent-surfaces.mjs index f29c0ca..26c9fa6 100644 --- a/apps/site/scripts/verify-agent-surfaces.mjs +++ b/apps/site/scripts/verify-agent-surfaces.mjs @@ -21,6 +21,27 @@ assert.ok(llmsFiles.some((file) => file === join(outputDirectory, "llms.txt")), const rootLlms = readFileSync(join(outputDirectory, "llms.txt"), "utf8"); assert.match(rootLlms, /^# Artifact Server\n\n> /, "/llms.txt must start with an H1 and project summary blockquote."); +const homepage = readFileSync(join(outputDirectory, "index.html"), "utf8"); +const installPromptButton = [...homepage.matchAll(//g)] + .map((match) => match[0]) + .find((button) => attribute(button, "data-copy-label") === "Copy the Artifact Server agent install prompt"); +assert.ok(installPromptButton, "The homepage is missing the agent install prompt."); + +const installPrompt = attribute(installPromptButton, "data-copy"); +assert.ok(installPrompt, "The agent install prompt is empty."); +for (const path of [ + "/docs/get-started/index.md", + "/docs/deploy/index.md", + "/docs/mcp/index.md", + "/docs/agents/index.md", + "/docs/index.md", +]) { + assert.ok( + installPrompt.includes(new URL(path, canonicalOrigin).href), + `The agent install prompt does not link to ${path}.`, + ); +} + for (const htmlFile of htmlFiles) { const html = readFileSync(htmlFile, "utf8"); const htmlPath = publicPath(htmlFile); diff --git a/apps/site/src/pages/index.astro b/apps/site/src/pages/index.astro index 33012ba..3ed93fd 100644 --- a/apps/site/src/pages/index.astro +++ b/apps/site/src/pages/index.astro @@ -37,10 +37,10 @@ const agents = [ const setupPrompt = `Install and configure Artifact Server for me. Do the work, not only explain the steps. Use these pages as the source of truth: -- https://artifactserver.com/docs/get-started/ -- https://artifactserver.com/docs/deploy/ -- https://artifactserver.com/docs/mcp/ -- https://artifactserver.com/docs/agents/ +- https://artifactserver.com/docs/get-started/index.md +- https://artifactserver.com/docs/deploy/index.md +- https://artifactserver.com/docs/mcp/index.md +- https://artifactserver.com/docs/agents/index.md First, ask me exactly one question: “Do you want to run Artifact Server locally on this machine, or deploy it for a team?” Wait for my answer before you continue. @@ -54,7 +54,7 @@ Then complete the matching setup: - Verify the server health, the review application, the installed skill, and MCP tool discovery. Fix failures that are within the setup scope. - Do not stop after producing a plan. Complete the setup unless you need a credential or a deployment choice from me. -When the setup is complete, begin your final response with “Artifact Server is running at .” State whether it is local or deployed for a team, where its data is stored, whether the skill is installed, whether this agent is connected through MCP, and what you verified. Link to https://artifactserver.com/docs/ for operating guidance. If anything is incomplete, list the exact blocker instead of claiming success.`; +When the setup is complete, begin your final response with “Artifact Server is running at .” State whether it is local or deployed for a team, where its data is stored, whether the skill is installed, whether this agent is connected through MCP, and what you verified. Link to https://artifactserver.com/docs/index.md for operating guidance. If anything is incomplete, list the exact blocker instead of claiming success.`; --- From 2cab2b53a62273db3452be6602c64ad54db03a73 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 2 Sep 2026 12:18:13 -0700 Subject: [PATCH 3/5] fix(site): hide hero review mockup on mobile --- apps/site/src/styles/landing.css | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/apps/site/src/styles/landing.css b/apps/site/src/styles/landing.css index 1459885..193481c 100644 --- a/apps/site/src/styles/landing.css +++ b/apps/site/src/styles/landing.css @@ -1362,19 +1362,6 @@ } .artifact-window { - height: 30rem; - box-shadow: 0.75rem 0.75rem 0 color-mix(in oklch, var(--nb-primary) 12%, transparent); - } - - .artifact-window::before { - display: none; - } - - .artifact-window__body { - grid-template-columns: 1fr; - } - - .artifact-canvas { display: none; } From 65d16f495fd504f8b72f709a84ed529067c396cd Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 2 Sep 2026 12:19:18 -0700 Subject: [PATCH 4/5] fix(site): focus review card on team feedback --- apps/site/src/pages/index.astro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/site/src/pages/index.astro b/apps/site/src/pages/index.astro index 3ed93fd..76edf78 100644 --- a/apps/site/src/pages/index.astro +++ b/apps/site/src/pages/index.astro @@ -222,9 +222,9 @@ When the setup is complete, begin your final response with “Artifact Server is
03 -

Review and share feedback.

-

Comments stay attached to the exact version and selected content. Share a review link so the next change starts with the full context.

- Comment → Agent → New version +

Review with your team.

+

Comments stay attached to the exact version and selected content. Share a review link so everyone can discuss the same work.

+ Review link → Team feedback → Next version
From 8bdb74233908746b488215b62c0c6afb74e64030 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 2 Sep 2026 12:49:27 -0700 Subject: [PATCH 5/5] docs(site): add deployment guides --- apps/site/.nimbus/routes.json | 4 + .../site/src/content/docs/docs/deploy/aws.mdx | 356 ++++++++++++++ .../content/docs/docs/deploy/cloudflare.mdx | 2 + .../src/content/docs/docs/deploy/compose.mdx | 446 ++++++++++++++++++ .../content/docs/docs/deploy/google-cloud.mdx | 380 +++++++++++++++ .../src/content/docs/docs/deploy/index.mdx | 68 +-- .../content/docs/docs/deploy/kubernetes.mdx | 338 +++++++++++++ apps/site/src/content/docs/docs/index.mdx | 4 + apps/site/src/pages/index.md.ts | 4 + packaging/compose/README.md | 5 +- project/spec/cloud-deployment-contract.md | 6 +- 11 files changed, 1573 insertions(+), 40 deletions(-) create mode 100644 apps/site/src/content/docs/docs/deploy/aws.mdx create mode 100644 apps/site/src/content/docs/docs/deploy/compose.mdx create mode 100644 apps/site/src/content/docs/docs/deploy/google-cloud.mdx create mode 100644 apps/site/src/content/docs/docs/deploy/kubernetes.mdx diff --git a/apps/site/.nimbus/routes.json b/apps/site/.nimbus/routes.json index 56c00c0..19c46d3 100644 --- a/apps/site/.nimbus/routes.json +++ b/apps/site/.nimbus/routes.json @@ -10,7 +10,11 @@ "/docs/concepts/security-boundary", "/docs/connect-agents", "/docs/deploy", + "/docs/deploy/aws", "/docs/deploy/cloudflare", + "/docs/deploy/compose", + "/docs/deploy/google-cloud", + "/docs/deploy/kubernetes", "/docs/get-started", "/docs/mcp", "/docs/publish", diff --git a/apps/site/src/content/docs/docs/deploy/aws.mdx b/apps/site/src/content/docs/docs/deploy/aws.mdx new file mode 100644 index 0000000..f487775 --- /dev/null +++ b/apps/site/src/content/docs/docs/deploy/aws.mdx @@ -0,0 +1,356 @@ +--- +title: Deploy on AWS +description: Deploy the live-qualified public AWS stack with ECS Fargate, RDS PostgreSQL, S3, CloudFront, and Pulumi. +sidebar: + order: 4 +--- + +The AWS Pulumi package creates an Artifact Server installation on ECS Fargate, RDS PostgreSQL, S3, CloudFront, and Route 53. + +The package does not create an EKS cluster. Use the [Kubernetes guide](/docs/deploy/kubernetes/) for an existing EKS cluster. + +## Current qualification + + + +The live run covered clean deployment, repeat preview, product operations, two-task scaling, S3 outage recovery, state recovery, backup, and restore. + +The run also covered image upgrade, image rollback, secret rotation, 100 concurrent read users, and safe removal of the qualification stack. + +Private ingress has not passed the same live lifecycle. Treat private ingress as unqualified. + +The local `pnpm test:aws-pulumi` command verifies the resource graph and configuration rules. This command does not access live AWS resources. + +Read the [AWS findings](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/aws/FINDINGS.md) for the failures and corrections from the live run. + +## Deployment architecture + +| Product need | AWS service | +| --- | --- | +| Application runtime | ECS Fargate on Linux ARM64 | +| Records | RDS PostgreSQL 17 | +| Artifact and staged-upload files | Versioned S3 bucket | +| Public HTTPS | CloudFront and an Application Load Balancer | +| Certificates | AWS Certificate Manager (ACM) | +| DNS | Route 53 | +| Application credentials | Secrets Manager | +| Workload identity | ECS task role | +| Logs | CloudWatch Logs | +| Staged-upload cleanup | EventBridge and a separate Fargate task | +| Infrastructure lifecycle | Pulumi | + +The generated network puts the application in private subnets. RDS has no public address and accepts PostgreSQL traffic only from application tasks. + +For public ingress, CloudFront serves the application host and the wildcard content host. The load balancer accepts HTTPS only from CloudFront addresses. + +## Prerequisites + +Prepare these items before you create a stack: + +- AWS credentials that can create the services in the architecture table. +- Node.js 24.12 or newer. +- pnpm 10.34.3. +- Pulumi CLI 3.257 or newer. +- An existing Pulumi state backend or Pulumi Cloud organization. +- An existing Pulumi secrets provider. +- Two Route 53 hosted zones for separate registrable domains. +- An OCI image digest that contains a Linux ARM64 image. +- When you enable browser sign-in, prepare a WorkOS AuthKit client and secret ARN. + + + +The main stack never creates the storage for its Pulumi state. Enable encryption, versioning, access control, backup, and update locking on that backend. + +## Prepare the Pulumi stack + + + + Run this command from the repository root. + + ```sh + pnpm install --frozen-lockfile + ``` + + + ```sh + cd deploy/pulumi/aws + ``` + + + Replace the example URL with your existing backend. + + ```sh + pulumi login s3://replace-with-existing-pulumi-state/artifact-server + ``` + + + If the stack exists, select it. + + ```sh + pulumi stack select production + ``` + + If the stack does not exist, create it with the selected secrets provider. + + ```sh + pulumi stack init production \ + --secrets-provider 'awskms://alias/artifact-server?region=us-east-1' + ``` + + + ```sh + cp Pulumi.production.example.yaml Pulumi.production.yaml + ``` + + + +## Configure the stack + +Edit `Pulumi.production.yaml`. Replace every example value. + +Use the [production configuration example](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/aws/Pulumi.production.example.yaml) as the complete key reference. + +| Configuration key | Rule | +| --- | --- | +| `applicationDomain` | Use the trusted application hostname. | +| `contentDomain` | Use a separate registrable domain for untrusted version hosts. | +| `imageReference` | Use an immutable `@sha256:` digest. | +| `installationName` | Use 1 to 40 lowercase letters, numbers, or hyphens. | +| `environment` | Use `development`, `staging`, or `production`. | +| `aws:region` and `region` | Use the same AWS region in both keys. | +| `capacity` | Set minimum tasks, maximum tasks, CPU, and memory. | +| `databasePlan` | Use `small`, `standard`, or `high-availability`. | +| `backupRetentionDays` | Use 7 to 35 days. Production requires at least 14 days. | +| `deletionProtection` | Use `true` for production. | +| `dnsZoneIds` | Supply different Route 53 zones for the application and content domains. | +| `stateBackendUrl` | Record the active Pulumi backend address. | +| `secretsProvider` | Record the active Pulumi secrets provider. | +| `stackName` | Match the active Pulumi stack name. | +| `workosClientId`, `workosIssuer`, `workosApiKeySecretRef` | Supply all three values, or omit all three values. | + +The package rejects unsafe CPU and memory combinations. It also rejects task counts that exceed the selected RDS connection budget. + +### Use the generated network + +The default network spans two availability zones. It contains public load-balancer subnets, private application subnets, and isolated database subnets. + +Production creates two NAT gateways. Development and staging create one NAT gateway. + +### Use an existing VPC + +Set every value in `existingNetwork`: + +```yaml +artifact-server-aws:existingNetwork: + vpcId: vpc-replace + loadBalancerSubnetIds: [subnet-lb-a, subnet-lb-b] + applicationSubnetIds: [subnet-app-a, subnet-app-b] + databaseSubnetIds: [subnet-db-a, subnet-db-b] +``` + +Each subnet group must belong to the VPC. Each subnet group must span at least two availability zones. + +Application subnets need outbound access to the OCI registry, S3, Secrets Manager, and the configured identity service. + +If you configure OTLP export, application subnets also need access to the telemetry endpoint. + +Database subnets must not have public routes. + +## Configure HTTPS and DNS + +### Public ingress + +Set `ingress: public`. Supply both Route 53 zone IDs. + +Pulumi creates regional ACM certificates for the load balancer. It also creates a CloudFront certificate in `us-east-1`. + +Pulumi creates Route 53 aliases for the application hostname and `*.contentDomain`. Both aliases point to CloudFront. + +CloudFront uses the origin `Cache-Control` headers. Private application and API responses use `private, no-store` and are not cached. + +### Private ingress + + + +Set `ingress: private`. Set `tlsCertificateArn` to an existing ACM certificate that covers both hostnames. + +The private stack creates an internal load balancer and no CloudFront distribution. + +If Pulumi manages private DNS, supply both `dnsZoneIds` values. Otherwise, create the private DNS records outside this stack. + +By default, private HTTPS accepts the VPC CIDR. Set `privateIngressCidrs` for other trusted IPv4 networks. + +## Configure identity and secrets + +Store the WorkOS API key in Secrets Manager before deployment. Put only its ARN in `workosApiKeySecretRef`. + +The stack creates the Artifact Server API credential and database URL. It stores both values in Secrets Manager. + +ECS injects these values into the container. Pulumi outputs contain only secret resource identifiers. + +The ECS task role uses temporary AWS credentials. Its S3 policy covers only the installation prefix. + +The package exposes typed WorkOS configuration. It does not expose typed configuration for a generic OIDC provider. + +## Preview and deploy + + + + Run this command from the repository root. + + ```sh + pnpm test:aws-pulumi + ``` + + + Run this command from `deploy/pulumi/aws`. + + ```sh + pulumi preview --stack production + ``` + + Verify the expected resources. Verify that no generated credential appears in the preview. + + + ```sh + pulumi up --stack production + ``` + + + ```sh + pulumi stack output deployment --stack production --json + ``` + + Keep this secret-free record with the release evidence. + + + +Each Fargate task applies compatible database migrations before the server starts. A PostgreSQL advisory lock serializes concurrent migration attempts. + +The ECS deployment circuit breaker rolls back tasks that do not become ready. + +## Verify the deployment + + + + ```sh + application_url=$(pulumi stack output applicationUrl --stack production) + ``` + + + ```sh + curl --fail --silent "$application_url/health" + ``` + + + ```sh + curl --fail --silent "$application_url/ready" + ``` + + + Open the application URL. Sign in with the bootstrap administrator email. + + + Publish one artifact. Open its exact-version Review link. + + + [Connect an agent to the MCP endpoint](/docs/mcp/). Verify tool discovery and an authenticated read. + + + +The deployment record also contains the health URL, readiness URL, MCP URL, log destination, and support manifest location. + +## Back up and restore + +The stack configures RDS automated backups and an RDS final snapshot. It also enables S3 versioning and blocks public bucket access. + +The stack does not create a coordinated backup service. A usable recovery point must contain matching RDS and S3 states. + +Create a production restore procedure with these operations: + +1. Suspend ECS autoscaling. +2. Stop all application tasks. +3. Create an RDS snapshot. +4. Copy the current S3 objects to a separate protected bucket. +5. Restore the original ECS capacity. +6. Restore the snapshot into a clean RDS instance. +7. Restore the object copy into a clean S3 bucket. +8. Run `artifactserver integrity check --mode external-storage` against the restored providers. +9. Verify the installation, project, artifact, version, and file identities. +10. Remove the temporary restore resources after the verification passes. + +Do not point an existing database at an unrelated S3 namespace. Preserve the installation identifier and the matching database and file set. + +The [tested AWS restore workflow](https://github.com/plannotator/artifact-server/blob/main/scripts/run-aws-coordinated-restore-qualification.sh) is qualification evidence. Adapt it to your recovery controls before production use. + +Back up the Pulumi state backend separately. The live qualification restored an exact checkpoint from a versioned S3 backend. + +## Update and roll back + +Record the current image digest before an update. Create a coordinated backup before a database migration. + + + + Use an immutable digest that contains a Linux ARM64 image. + + ```sh + pulumi config set imageReference \ + 'repository@sha256:replace_with_64_hex_characters' \ + --stack production + ``` + + + ```sh + pulumi preview --stack production + ``` + + + ```sh + pulumi up --stack production + ``` + + + Verify health, readiness, browser access, artifact delivery, and MCP access. + + + +For rollback, set `imageReference` to the previous digest. Then preview and apply the stack again. + +Use only a release that supports the current database schema. The live qualification covered one schema-compatible update and rollback. + +## Remove a stack + + + +The S3 bucket uses `forceDestroy: false`. Pulumi cannot delete a nonempty bucket. + +Before permanent removal, verify a coordinated backup and a clean restore. Remove protection only through a separately approved procedure. + +For an approved, unprotected, empty non-production stack, run: + +```sh +pulumi destroy --stack staging +``` + +RDS creates a final snapshot during removal. Verify that the stack has no remaining managed resources. + +## Known limits + +- Only public ingress has passed the live AWS lifecycle. +- The stack uses ARM64 Fargate tasks. +- The stack does not create EKS. +- The stack does not create its Pulumi state backend. +- The stack does not automate coordinated RDS and S3 backups. +- The Pulumi package exposes WorkOS inputs but no typed generic OIDC inputs. +- The first public Artifact Server image is not available yet. +- Complete signed release evidence and full product conformance remain open. + +Read the [AWS package guide](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/aws/README.md) for the source-level resource details and qualification commands. diff --git a/apps/site/src/content/docs/docs/deploy/cloudflare.mdx b/apps/site/src/content/docs/docs/deploy/cloudflare.mdx index 4197430..815e72e 100644 --- a/apps/site/src/content/docs/docs/deploy/cloudflare.mdx +++ b/apps/site/src/content/docs/docs/deploy/cloudflare.mdx @@ -1,6 +1,8 @@ --- title: Deploy on Cloudflare description: The live-qualified Artifact Server deployment path that uses Workers, D1, R2, and Alchemy. +sidebar: + order: 1 --- Cloudflare is Artifact Server's live-qualified direct-cloud target. The deployment package runs the same product services on Cloudflare infrastructure. diff --git a/apps/site/src/content/docs/docs/deploy/compose.mdx b/apps/site/src/content/docs/docs/deploy/compose.mdx new file mode 100644 index 0000000..6a4f5f9 --- /dev/null +++ b/apps/site/src/content/docs/docs/deploy/compose.mdx @@ -0,0 +1,446 @@ +--- +title: Deploy with Compose +description: Run Artifact Server on one server with compact or external storage. +sidebar: + order: 2 +--- + +Compose provides two team deployment modes. Both modes use the same OCI image and private HTTP service. + +| Mode | Application processes | Records | Artifact files | Use this mode when | +| --- | --- | --- | --- | --- | +| Compact Compose | Exactly one | SQLite | One file volume | One server owns all durable data. | +| External-storage Compose | One or more | Existing PostgreSQL | Existing S3-compatible storage | Your team operates the durable providers separately. | + + + + + +## Choose a mode + +Use Compact Compose for the shortest one-server team installation. This mode does not provide application failover or horizontal scaling. + +Use external-storage Compose for multiple application processes. This mode requires PostgreSQL and supported S3-compatible storage before startup. + +Artifact Server does not implement transfer from compact storage to external storage. Select the durable storage model before production use. + +## Meet the shared requirements + +Both modes require these items: + +- Docker with Docker Compose. +- An immutable Artifact Server image digest for the release. +- One HTTPS application origin, such as `https://artifacts.example.com`. +- One separate registrable content domain, such as `content.example.net`. +- Wildcard DNS and TLS for `*.content.example.net`. +- One OIDC or WorkOS browser-login provider. +- A trusted reverse proxy or gateway. + +The package does not install DNS, certificates, a reverse proxy, PostgreSQL, or object storage. + +## Prepare the server + +Copy the Compose package from the source checkout. Then work from the copied directory. + +```sh +cp -R packaging/compose /srv/artifact-server +cd /srv/artifact-server +cp .env.example .env +``` + +Edit `.env`. Set the shared configuration: + +```dotenv +ARTIFACT_SERVER_IMAGE=ghcr.io/plannotator/artifact-server@sha256:replace_with_release_digest +ARTIFACT_SERVER_BIND_ADDRESS=127.0.0.1 +ARTIFACT_SERVER_PORT=8787 +ARTIFACT_SERVER_ORIGIN=https://artifacts.example.com +ARTIFACT_SERVER_CONTENT_DOMAIN=content.example.net +ARTIFACT_SERVER_READINESS_WITHDRAWAL_MS=1000 +ARTIFACT_SERVER_SHUTDOWN_DEADLINE_MS=10000 +ARTIFACT_SERVER_REQUEST_LOG_SAMPLE_RATE=0.01 +``` + +The application origin and content domain must have different registrable domains. This rule keeps untrusted artifact content outside the trusted application origin. + +[Read the security boundary →](/docs/concepts/security-boundary/) + +## Configure browser login + +Configure exactly one browser-login provider. A partial configuration or two provider families cause startup to fail. + +### OIDC + +Register this redirect URI with the OIDC provider: + +```text +https://artifacts.example.com/auth/callback +``` + +Set these values in `.env`: + +```dotenv +ARTIFACT_SERVER_OIDC_ISSUER=https://idp.example.com/realms/main +ARTIFACT_SERVER_OIDC_CLIENT_ID=artifact-server +ARTIFACT_SERVER_OIDC_SCOPES=openid email profile +``` + +The client secret is optional for a public client that uses PKCE. A confidential client requires one secret source. + +Use `ARTIFACT_SERVER_OIDC_CLIENT_SECRET_FILE` for a mounted secret file. Add a private Compose override that mounts this file read-only. + +### WorkOS + +Set the exact AuthKit issuer and client ID: + +```dotenv +ARTIFACT_SERVER_WORKOS_ISSUER=https://replace.authkit.app +ARTIFACT_SERVER_WORKOS_CLIENT_ID=client_replace_me +``` + +WorkOS also requires `ARTIFACT_SERVER_WORKOS_API_KEY_FILE`. Add a private Compose override that mounts this file read-only. + +Use one dedicated WorkOS environment for each Artifact Server environment. + +## Configure the reverse proxy + +Route the application hostname and all wildcard content hosts to port 8787. Preserve the requested host and HTTPS protocol. + +The proxy must support streamed uploads, response streaming, required timeouts, and the configured upload limits. + +Keep the default loopback bind for one replica behind a host-level proxy. Attach a network gateway for multiple external-storage replicas. + +Caddy, Nginx, Traefik, HAProxy, or an existing gateway can provide this boundary. The Compose package does not select one. + +Public artifact access removes the Artifact Server login check. It does not make a private server reachable from the internet. + +## Run Compact Compose + +Compact Compose stores SQLite, artifact files, staged uploads, and generated secrets in the `artifact-server-data` volume. + +Verify that the server has space for the active volume, one backup copy, and expected growth. + + + + ```sh + docker compose config + ``` + + + ```sh + docker compose run --rm --no-deps artifact-server \ + init --admin-email admin@example.com \ + --data /var/lib/artifact-server/data + ``` + + The command creates the installation identity and machine API credential. It prints only the installation ID and data directory. + + + ```sh + docker compose up --detach --wait + ``` + + + ```sh + curl --fail http://127.0.0.1:8787/health + curl --fail http://127.0.0.1:8787/ready + ``` + + Then open the configured HTTPS application origin through the reverse proxy. + + + +Do not scale the compact service. The fixed container name makes a second writer fail during Compose startup. + +The volume needs reliable SQLite locking and atomic replacement. NFS-like filesystems do not have qualification for this mode. + +### Back up Compact Compose + +Run the included backup script with a new target directory: + +```sh +./compact-backup.sh /srv/backups/artifact-server/2026-09-02 +``` + +The script stops the service before it copies data. Then it writes these files: + +- `data.tar` +- `data.tar.sha256` +- `support-manifest.json` + +The script restarts the service after a successful copy. A failed backup retains an `INCOMPLETE` marker. + +### Restore Compact Compose + +Stop the target service. Use an empty target volume and a new Compose project name for a recovery drill. + +```sh +export COMPOSE_PROJECT_NAME=artifact-server-restored +./compact-restore.sh /srv/backups/artifact-server/2026-09-02 +docker compose up --detach --wait +``` + +The restore script verifies the checksum, archive paths, filesystem entries, and complete installation integrity. + +The script rejects running targets, nonempty volumes, incomplete backups, unsafe paths, links, special files, and checksum failures. + +If you restore the same installation, keep the application origin and content domain unchanged. + +Verify the installation ID, projects, artifacts, versions, actions, access configuration, manifests, and exact file bytes. + +## Run external-storage Compose + +External-storage Compose uses the base file and `compose.external-storage.yaml`. The override removes the compact volume and fixed container name. + +Provide these dependencies before startup: + +- One PostgreSQL database for this installation. +- One AWS S3 or Cloudflare R2 bucket. +- One gateway on the Compose network for multiple replicas. +- Enough database connections for every replica and migration process. + +Another S3-compatible provider requires successful Artifact Server adapter contract tests. Provider compatibility text alone is not qualification. + +MinIO is an integration-test provider. The production package does not install or recommend MinIO. + +### Create the required secret files + +Create the secret directory and machine API credential: + +```sh +install -d -o 1000 -g 1000 -m 0700 /etc/artifact-server +printf 'as_key_key_%s_%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 32)" \ + > /etc/artifact-server/api-token +chown 1000:1000 /etc/artifact-server/api-token +chmod 0400 /etc/artifact-server/api-token +``` + +Write the complete PostgreSQL URL to its secret file: + +```sh +printf '%s\n' 'postgresql://artifactserver:replace_me@postgres.example/artifactserver' \ + > /etc/artifact-server/database-url +chown 1000:1000 /etc/artifact-server/database-url +chmod 0400 /etc/artifact-server/database-url +``` + +The image runs with UID and GID `1000:1000`. Each mounted secret file must use this ownership and mode `0400`. + +Do not put the machine API credential or PostgreSQL URL in `.env`. + +### Configure the external providers + +Add these values to `.env`: + +```dotenv +ARTIFACT_SERVER_INSTALLATION_ID=artifact-server-production +ARTIFACT_SERVER_BOOTSTRAP_ADMIN_EMAIL=admin@example.com +ARTIFACT_SERVER_API_TOKEN_SECRET_FILE=/etc/artifact-server/api-token +ARTIFACT_SERVER_DATABASE_URL_SECRET_FILE=/etc/artifact-server/database-url +ARTIFACT_SERVER_POSTGRES_MAX_CONNECTIONS=10 +ARTIFACT_SERVER_S3_BUCKET=replace-me +ARTIFACT_SERVER_S3_REGION=us-east-1 +ARTIFACT_SERVER_S3_FORCE_PATH_STYLE=false +``` + +Keep `ARTIFACT_SERVER_INSTALLATION_ID` stable for the complete installation lifetime. + +AWS installations can use an instance role or another AWS workload identity. Do not add static S3 keys for that configuration. + +For R2 or another qualified provider, copy the static-credential overlay: + +```sh +cp compose.external-storage.s3-credentials.yaml.example \ + compose.external-storage.s3-credentials.yaml +``` + +Create both S3 credential files with UID and GID `1000:1000`. Set each file mode to `0400`. + +Set these configuration values for the private overlay: + +```dotenv +ARTIFACT_SERVER_S3_ENDPOINT=https://replace-me.r2.cloudflarestorage.com +ARTIFACT_SERVER_S3_ACCESS_KEY_ID_SECRET_FILE=/etc/artifact-server/s3-access-key-id +ARTIFACT_SERVER_S3_SECRET_ACCESS_KEY_SECRET_FILE=/etc/artifact-server/s3-secret-access-key +``` + +If you use static credentials, add `--file compose.external-storage.s3-credentials.yaml` to every external-storage command. + +### Verify the configuration and migrate PostgreSQL + +Parse the complete configuration and connect to both providers: + +```sh +docker compose --file compose.yaml --file compose.external-storage.yaml \ + run --rm --no-deps artifact-server \ + config check --mode external-storage +``` + +Verify the current schema: + +```sh +docker compose --file compose.yaml --file compose.external-storage.yaml \ + run --rm --no-deps artifact-server migrate status +``` + +Apply the release migrations: + +```sh +docker compose --file compose.yaml --file compose.external-storage.yaml \ + run --rm --no-deps artifact-server migrate apply +``` + +Serving containers verify the schema. They never apply migrations during startup. + +### Start one replica + +Use the loopback overlay for one replica behind a host-level reverse proxy: + +```sh +docker compose --file compose.yaml \ + --file compose.external-storage.yaml \ + --file compose.external-storage.local-port.yaml \ + up --detach --wait +``` + +Verify the local process: + +```sh +curl --fail http://127.0.0.1:8787/health +curl --fail http://127.0.0.1:8787/ready +``` + +Do not combine the fixed local-port overlay with multiple replicas. + +### Start multiple replicas + +Attach the reverse proxy or gateway to the Compose network. Then start the replicas: + +```sh +docker compose --file compose.yaml --file compose.external-storage.yaml \ + up --detach --scale artifact-server=2 --wait +``` + +Route both HTTPS host families to the `artifact-server` service on port 8787. + +Set `ARTIFACT_SERVER_POSTGRES_MAX_CONNECTIONS` from the PostgreSQL connection budget. Each replica uses its own bounded pool. + +The package does not configure autoscaling, load balancing, or multi-region failover. + +### Back up external storage + +PostgreSQL and the complete Artifact Server object prefix form one backup unit. A database-only or bucket-only backup is incomplete. + + + + ```sh + docker compose --file compose.yaml --file compose.external-storage.yaml \ + stop artifact-server + ``` + + + Create one transaction-consistent logical PostgreSQL backup with the provider procedure. + + + Copy the complete Artifact Server installation prefix with the provider procedure. + + + ```sh + docker compose --file compose.yaml --file compose.external-storage.yaml \ + run --rm --no-deps artifact-server \ + support manifest --mode external-storage + ``` + + Store this output and checksums with both backup parts. + + + Restore both parts into empty providers. Then run the integrity command: + + ```sh + docker compose --file compose.yaml --file compose.external-storage.yaml \ + run --rm --no-deps artifact-server \ + integrity check --mode external-storage + ``` + + + +Start the application only after the integrity report has status `healthy`. + +## Update the application image + +Cross-version update and rollback compatibility does not have a release qualification yet. Do not change versions without release-specific compatibility instructions. + +When release instructions permit an update, use this sequence: + + + + Use the backup procedure for the selected storage mode. + + + Replace `ARTIFACT_SERVER_IMAGE` with the exact digest from the release instructions. + + + Run `docker compose config` for compact mode. Run `config check` for external-storage mode. + + + For external-storage mode, run `migrate status`. Then run `migrate apply`. + + + Run `docker compose up --detach --force-recreate --wait` with the files for the selected mode. + + + Verify `/health`, `/ready`, browser sign-in, one private artifact, and one public artifact. + + + +The prior image must accept the current schema before rollback. Artifact Server does not apply an automatic down-migration. + +## Remove the Compose application + +Stop and remove compact application containers while you preserve the data volume: + +```sh +docker compose down +``` + + + +Stop and remove external-storage application containers with both Compose files: + +```sh +docker compose --file compose.yaml --file compose.external-storage.yaml down +``` + +The external command does not remove PostgreSQL or object storage. Those providers are outside the Compose package. + +## Known limits + +- Compact Compose supports exactly one application process. +- Compact Compose does not support NFS-like data volumes without separate qualification. +- External-storage Compose does not install or back up its durable providers. +- External-storage Compose does not configure autoscaling or multi-region failover. +- The package does not configure a reverse proxy, DNS, TLS, or firewall rules. +- Cross-version update and rollback compatibility does not have qualification. +- Compact-to-external-storage transfer is not implemented. + +## Read the package sources + +- [Compose package instructions](https://github.com/plannotator/artifact-server/blob/main/packaging/compose/README.md) +- [Compact Compose file](https://github.com/plannotator/artifact-server/blob/main/packaging/compose/compose.yaml) +- [External-storage Compose file](https://github.com/plannotator/artifact-server/blob/main/packaging/compose/compose.external-storage.yaml) +- [Compact recovery decision](https://github.com/plannotator/artifact-server/blob/main/project/spec/decisions/0011-compact-compose-recovery.md) +- [External-storage decision](https://github.com/plannotator/artifact-server/blob/main/project/spec/decisions/0012-external-storage-compose.md) +- [Compact release test](https://github.com/plannotator/artifact-server/blob/main/tests/release/compact-compose.test.ts) +- [External-storage release test](https://github.com/plannotator/artifact-server/blob/main/tests/release/external-storage-compose.test.ts) diff --git a/apps/site/src/content/docs/docs/deploy/google-cloud.mdx b/apps/site/src/content/docs/docs/deploy/google-cloud.mdx new file mode 100644 index 0000000..38c05e2 --- /dev/null +++ b/apps/site/src/content/docs/docs/deploy/google-cloud.mdx @@ -0,0 +1,380 @@ +--- +title: Deploy on Google Cloud +description: Deploy Artifact Server on Cloud Run, Cloud SQL for PostgreSQL, and Google Cloud Storage with the checked-in Pulumi project. +sidebar: + order: 5 +--- + +The Google Cloud package deploys the external-storage runtime with Pulumi. It creates Cloud Run, Cloud SQL, Google Cloud Storage, DNS, TLS, and edge resources. + + + +The live run covered product operations, bounded reads, upgrades, rollback, API-secret rotation, state import, provider restore operations, and safe deletion. The release ledger still requires complete signed evidence and full product conformance. + +## Deployment architecture + +| Product need | Google Cloud service | +| --- | --- | +| Application runtime | Cloud Run | +| Durable records | Cloud SQL for PostgreSQL 17 | +| Artifact and staged-upload bytes | Google Cloud Storage | +| Public edge | External Application Load Balancer and Cloud CDN | +| DNS and TLS | Cloud DNS and Certificate Manager | +| Application credentials | Secret Manager | +| Workload identity | Dedicated Cloud Run service account | +| Expired-upload cleanup | Cloud Run Job and Cloud Scheduler | +| Provisioning and lifecycle | Pulumi | + +Cloud Run accepts traffic from the load balancer. The package disables the default Cloud Run URL. + +The load balancer serves the application host and wildcard content hosts. Cloud CDN follows origin cache headers and bypasses every request with a `Range` header. + +Cloud SQL uses private IP. The storage bucket blocks public access and uses uniform bucket access. + +## Prerequisites + +Prepare these items before you create a stack: + +- A dedicated Google Cloud project. +- A source checkout with Node.js 24.12 or later and pnpm 10.34.3. +- The Pulumi CLI and Google Cloud CLI. +- Application Default Credentials for the Pulumi process. +- An existing versioned GCS bucket or Pulumi Cloud for Pulumi state. +- A Pulumi secrets provider, such as one Cloud KMS key. +- A multi-architecture Artifact Server image pinned to one SHA-256 digest. +- Two separate registrable domains. +- One authoritative Cloud DNS managed zone for each domain. + +The Pulumi identity must manage Service Usage, IAM, Cloud Run, Cloud SQL, GCS, Compute, DNS, Certificate Manager, Secret Manager, and Cloud Scheduler. + +The identity must also change records in both existing Cloud DNS managed zones. The repository does not define a minimum operator IAM role. + +The stack enables these Google Cloud APIs: + +```text +certificatemanager.googleapis.com +cloudscheduler.googleapis.com +compute.googleapis.com +dns.googleapis.com +run.googleapis.com +secretmanager.googleapis.com +servicenetworking.googleapis.com +sqladmin.googleapis.com +storage.googleapis.com +``` + +If you use Cloud KMS for Pulumi secrets, enable `cloudkms.googleapis.com` before you create the stack. + +## Prepare Pulumi + +Install the repository dependencies: + +```sh +pnpm install --frozen-lockfile +``` + +Authenticate Pulumi through Application Default Credentials. For an interactive shell, run these commands: + +```sh +gcloud auth application-default login +gcloud config set project YOUR_GCP_PROJECT_ID +``` + +Select the external state backend: + +```sh +pulumi login gs://YOUR_STATE_BUCKET/artifact-server +``` + +Create the stack with its secrets provider: + +```sh +pulumi stack init production \ + --cwd deploy/pulumi/gcp \ + --secrets-provider gcpkms://projects/YOUR_GCP_PROJECT_ID/locations/global/keyRings/YOUR_KEY_RING/cryptoKeys/YOUR_KEY +``` + +If the stack exists, select it instead: + +```sh +pulumi stack select production --cwd deploy/pulumi/gcp +``` + +Enable object versioning on the state bucket. The checked-in state recovery probe rejects a GCS backend without versioning. + +## Configure the stack + +Create `deploy/pulumi/gcp/Pulumi.production.yaml`. Replace every example value before you run a preview. + +```yaml +config: + gcp:project: YOUR_GCP_PROJECT_ID + gcp:region: us-central1 + artifact-server-gcp:applicationDomain: artifacts.example.com + artifact-server-gcp:backupRetentionDays: 14 + artifact-server-gcp:bootstrapAdministratorEmail: admin@example.com + artifact-server-gcp:capacity: + cpu: 1 + maximumInstances: 3 + memoryMiB: 2048 + minimumInstances: 2 + artifact-server-gcp:contentDomain: artifact-content.example.net + artifact-server-gcp:databasePlan: high-availability + artifact-server-gcp:deletionProtection: true + artifact-server-gcp:dnsZoneIds: + application: artifacts-example-com + content: artifact-content-example-net + artifact-server-gcp:environment: production + artifact-server-gcp:imageReference: ghcr.io/plannotator/artifact-server@sha256:REPLACE_WITH_64_HEX_CHARACTERS + artifact-server-gcp:ingress: public + artifact-server-gcp:installationName: artifact-server + artifact-server-gcp:region: us-central1 + artifact-server-gcp:secretsProvider: gcpkms://projects/YOUR_GCP_PROJECT_ID/locations/global/keyRings/YOUR_KEY_RING/cryptoKeys/YOUR_KEY + artifact-server-gcp:stackName: production + artifact-server-gcp:stateBackendUrl: gs://YOUR_STATE_BUCKET/artifact-server +``` + +`dnsZoneIds.application` and `dnsZoneIds.content` are managed-zone names. They are not project numbers or DNS zone numbers. + +The application and content domains must have different registrable domains. Public ingress requires both DNS zone values. + +Production configuration requires these values: + +- `minimumInstances` is 1 or more. +- `backupRetentionDays` is 14 through 35. +- `deletionProtection` is `true`. +- `imageReference` contains one immutable SHA-256 digest. + +Optional configuration includes `otlpEndpoint`, `requestLogSampleRate`, and `resourceTags`. The request-log sample rate defaults to `0.01`. + +The GCP package accepts 0.25, 0.5, 1, 2, 4, or 8 CPUs. Memory must be 512 MiB through 32,768 MiB. + +| Database plan | Cloud SQL availability | Tier | Initial disk | Connection budget | +| --- | --- | --- | --- | --- | +| `small` | Zonal | `db-custom-1-3840` | 20 GiB | 80 | +| `standard` | Zonal | `db-custom-2-7680` | 50 GiB | 200 | +| `high-availability` | Regional | `db-custom-4-15360` | 100 GiB | 500 | + +The package reserves connections for a rolling replacement. It rejects a maximum instance count that exceeds the selected database budget. + +### Use an existing network + +The package creates a VPC, a `/24` regional subnet, and private service networking by default. + +Set `existingNetwork` to use customer-owned network resources: + +```yaml + artifact-server-gcp:existingNetwork: + privateServiceConnection: projects/YOUR_GCP_PROJECT_ID/global/networks/YOUR_VPC/peerings/YOUR_SQL_PEERING + vpcEgressConfiguration: projects/YOUR_GCP_PROJECT_ID/regions/us-central1/subnetworks/YOUR_SUBNET + vpcName: projects/YOUR_GCP_PROJECT_ID/global/networks/YOUR_VPC +``` + +Make sure that private services access works before deployment. Reserve enough subnet addresses for the maximum Cloud Run instance count. + +## Configure authentication and secrets + +The stack generates an API token and database password. It stores both values in Secret Manager and omits them from stack outputs. + +The Cloud Run service account receives access to generated secrets, the artifact bucket, and Cloud SQL. It uses Application Default Credentials for GCS. + +The direct GCP package exposes typed WorkOS configuration. It does not expose typed generic OIDC configuration. + +The stack can start without browser login configuration. Do not treat that mode as a team-ready installation. + +To enable WorkOS, set all three values together: + +```yaml + artifact-server-gcp:workosApiKeySecretRef: YOUR_WORKOS_SECRET_ID + artifact-server-gcp:workosClientId: client_YOUR_CLIENT_ID + artifact-server-gcp:workosIssuer: https://YOUR_TENANT.authkit.app +``` + +`workosApiKeySecretRef` identifies an existing Secret Manager secret. It does not contain the WorkOS API key. + +Grant the generated Cloud Run service account `roles/secretmanager.secretAccessor` on this secret. The Pulumi project does not create that grant. + +## Preview and deploy + +Run the package verification before you create cloud resources: + +```sh +pnpm --dir deploy/pulumi/gcp verify +``` + +Review the infrastructure preview: + +```sh +pulumi preview --cwd deploy/pulumi/gcp --stack production +``` + +Create or update the stack: + +```sh +pulumi up --cwd deploy/pulumi/gcp --stack production +``` + +Certificate Manager creates DNS authorization records for both domains. Certificate issuance and DNS changes can delay the first healthy response. + +## Verify the deployment + +Save the secret-free deployment output: + +```sh +pulumi stack output deployment \ + --cwd deploy/pulumi/gcp \ + --stack production \ + --json > deployment.json +``` + +Verify the health and readiness endpoints: + +```sh +curl --fail --silent --show-error "$(jq -r '.healthUrl' deployment.json)" +curl --fail --silent --show-error "$(jq -r '.readinessUrl' deployment.json)" +``` + +Open the `applicationUrl` from `deployment.json`. If you configured WorkOS, verify browser sign-in. + +Verify publication, private access, version reads, comments, and MCP discovery. + +The output also contains the runtime, database, bucket, network, secret, state, identity, log, and support-manifest resource identifiers. + + + +Run the repeatable product probe against an isolated stack: + +```sh +PULUMI_BACKEND_URL=gs://YOUR_STATE_BUCKET/artifact-server \ +ARTIFACT_SERVER_GCP_QUALIFICATION_STACK=gcp-qualification \ + scripts/run-gcp-deployment-product-qualification.sh +``` + +## Back up and restore + +Cloud SQL keeps scheduled backups and point-in-time recovery. The selected plan retains 7 through 35 backups. + +The GCS bucket enables object versioning and soft deletion. Its soft-delete period equals `backupRetentionDays`. + +These independent controls do not form one coordinated recovery point. Quiesce writes before you back up both data stores. + +Create one Cloud SQL backup. Then copy the complete artifact bucket. Record both provider results as one recovery set. + +Restore the database into an empty private instance. Then restore the objects into an empty private bucket. + +Verify installation IDs, project IDs, artifact IDs, version IDs, names, sizes, and checksums. + +The live qualification restored Cloud SQL and copied 29 GCS objects with exact names, sizes, and CRC32C checksums. A later restored installation passed the integrity scanner. + +The repository does not provide automated disaster recovery or cross-region failover. The release ledger still requires detailed application-level restore evidence. + +## Update and roll back + +Create one coordinated recovery set before you update the stack. + +Record the current image digest before an update: + +```sh +pulumi config get imageReference \ + --cwd deploy/pulumi/gcp \ + --stack production +``` + +Set the new digest-pinned image: + +```sh +pulumi config set imageReference REGISTRY/IMAGE@sha256:NEW_DIGEST \ + --cwd deploy/pulumi/gcp \ + --stack production +``` + +Preview the update. Then apply it: + +```sh +pulumi preview --cwd deploy/pulumi/gcp --stack production +pulumi up --cwd deploy/pulumi/gcp --stack production +``` + +Verify the product after the update. The container applies database migrations before it starts the server. + +If the image is schema-compatible, set the prior digest. Then run `pulumi up` again. + +Do not use an incompatible image for rollback. + +The live qualification preserved the installation, database, and bucket identities during its update and rollback. + +## Recover Pulumi state + +Keep the GCS state bucket outside this stack. Enable object versioning. + +Protect the bucket with its own access controls. + +Pulumi state recovery depends on the selected backend. Follow the Pulumi recovery procedure for that backend. + +The checked-in probe exports and imports one exact checkpoint. It then compares resource identities and requires a no-change preview. + +Run this probe only against an isolated stack: + +```sh +PULUMI_BACKEND_URL=gs://YOUR_STATE_BUCKET/artifact-server \ +ARTIFACT_SERVER_GCP_QUALIFICATION_STACK=gcp-qualification \ + scripts/run-gcp-state-recovery-qualification.sh +``` + +## Delete a stack + + + +Production configuration requires deletion protection. The package does not include a procedure that disables production protection. + +For an unprotected non-production stack, run the native destroy command: + +```sh +pulumi destroy \ + --cwd deploy/pulumi/gcp \ + --stack staging \ + --yes \ + --non-interactive +``` + +If the artifact bucket stops deletion, back up its contents. Then remove every object version. + +Run the same destroy command again. + +Cloud Run can retain subnet addresses for one or two hours. If an address blocks deletion, wait for Google to release it. + +Then run the same destroy command. Do not manually delete a `serverless-ipv4-*` address. + +Cloud SQL can retain producer network resources for up to four days. The package abandons the connection while Google completes that cleanup. + +The stack does not own the Pulumi state bucket or image repository. Delete those prerequisites in separate, explicit operations. + +## Known limits + +- The package supports public ingress only. +- The package is pre-release. +- The direct package exposes WorkOS inputs but not generic OIDC inputs. +- An installation without WorkOS does not provide team browser login. +- The package does not automate disaster recovery or cross-region failover. +- The repository does not define a minimum operator IAM role. +- Production teardown requires an operator-owned deprotection procedure. +- Complete signed release evidence and full product conformance remain open. + +## Source and evidence + +- [Google Cloud Pulumi package](https://github.com/plannotator/artifact-server/tree/main/deploy/pulumi/gcp) +- [Package configuration and qualification status](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/gcp/README.md) +- [Shared cloud deployment contract](https://github.com/plannotator/artifact-server/blob/main/project/spec/cloud-deployment-contract.md) +- [Live product evidence](https://github.com/plannotator/artifact-server/blob/main/project/evidence/gcp-deployment-product.json) +- [Update and rollback evidence](https://github.com/plannotator/artifact-server/blob/main/project/evidence/gcp-upgrade-rollback.json) +- [State recovery evidence](https://github.com/plannotator/artifact-server/blob/main/project/evidence/gcp-state-recovery.json) +- [Backup and restore evidence](https://github.com/plannotator/artifact-server/blob/main/project/evidence/gcp-phase11-qualification.json) +- [Safe deletion evidence](https://github.com/plannotator/artifact-server/blob/main/project/evidence/gcp-destroy.json) diff --git a/apps/site/src/content/docs/docs/deploy/index.mdx b/apps/site/src/content/docs/docs/deploy/index.mdx index 078d8d8..9f884d3 100644 --- a/apps/site/src/content/docs/docs/deploy/index.mdx +++ b/apps/site/src/content/docs/docs/deploy/index.mdx @@ -1,64 +1,64 @@ --- title: Deploy for a team -description: Compare the tested deployment targets and the boundaries that every remote installation uses. +description: Compare the deployment targets, data layers, and verified qualification boundaries. --- -Artifact Server runs locally and on four server shapes. The qualification level differs by target. +Artifact Server provides five team deployment guides. The Compose guide covers compact and external-storage modes. ## Choose a deployment -| Deployment | Data layer | Qualification | Guide | +| Deployment | Data layer | Verified qualification | Guide | | --- | --- | --- | --- | | Cloudflare | D1 and R2 | Live staging passed | [Deploy on Cloudflare](/docs/deploy/cloudflare/) | -| Compact Compose | SQLite and one file volume | Automated runtime passed | [Compose guide](https://github.com/plannotator/artifact-server/blob/main/packaging/compose/README.md) | -| External-storage Compose | PostgreSQL and S3-compatible storage | Automated runtime passed | [Compose guide](https://github.com/plannotator/artifact-server/blob/main/packaging/compose/README.md) | -| Kubernetes | PostgreSQL and object storage | Helm and kind verification passed | [Helm guide](https://github.com/plannotator/artifact-server/blob/main/packaging/helm/artifact-server/README.md) | -| AWS (Amazon Web Services) | ECS, RDS, and S3 | Pulumi verification passed. No live application deployment. | [AWS Pulumi guide](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/aws/README.md) | -| Google Cloud (GCP) | Cloud Run, Cloud SQL, and Cloud Storage | Pulumi verification passed. No live application deployment. | [Google Cloud Pulumi guide](https://github.com/plannotator/artifact-server/blob/main/deploy/pulumi/gcp/README.md) | +| Compact Compose | SQLite and one file volume | Automated runtime passed | [Deploy with Compose](/docs/deploy/compose/) | +| External-storage Compose | PostgreSQL and S3-compatible storage | Automated runtime passed | [Deploy with Compose](/docs/deploy/compose/) | +| Kubernetes | PostgreSQL and object storage | Helm and disposable kind-cluster verification passed | [Deploy on Kubernetes](/docs/deploy/kubernetes/) | +| AWS (Amazon Web Services) | ECS Fargate, RDS, and S3 | Default public stack passed live isolated qualification | [Deploy on AWS](/docs/deploy/aws/) | +| Google Cloud (GCP) | Cloud Run, Cloud SQL, and Cloud Storage | Public stack passed live isolated qualification | [Deploy on Google Cloud](/docs/deploy/google-cloud/) | -Cloudflare is the live-qualified hosted target. [Cloudflare Artifacts](/docs/deploy/cloudflare/) can add private Git history for selected projects. +The AWS private-ingress variant has not passed its live lifecycle gate. The Google Cloud package rejects private ingress. -AWS and Google Cloud include verified Pulumi projects. Their shared PostgreSQL and object-storage runtime passes automated verification. Neither installer completed a live application deployment. - -Azure teams use the Helm chart on AKS. Artifact Server has no separate Azure installer. The Azure Blob Storage adapter remains a preview. +Use the Kubernetes guide for EKS, GKE, or AKS. Artifact Server does not provide a separate Azure installer. -## The boundaries every remote deployment shares +## Meet the remote deployment requirements - - A single HTTPS origin serves Artifact Server, its API, and its MCP endpoint. This is where people sign in and where agents connect. + + Use one HTTPS origin for the application, API, and MCP endpoint. People sign in at this origin. - - Untrusted artifact files are served from a separate wildcard domain, one hostname per version. The application origin never serves artifact bytes. [Why the two origins are separate →](/docs/concepts/security-boundary/) + + Use a separate registrable domain with wildcard TLS. Each artifact version receives its own hostname. + + [Read about the two-origin security boundary →](/docs/concepts/security-boundary/) - - Remote deployments authenticate through WorkOS or one generic OIDC provider. Local-owner access works only on an exact loopback origin. Remote deployments never use local-owner access. Network access and application authorization remain separate controls. An administrator admits team members inside Artifact Server. + + Use WorkOS or one generic OIDC provider. Remote deployments do not use local-owner access. - - Service credentials live in the platform's secret store, never in the repository or the image. + + Store service credentials in the deployment platform's secret store. Keep credentials out of source control and deployment outputs. - - Back up metadata and artifact files as one recovery set. Use the procedure in the selected guide. Do a restore test before the first production release. Do another restore test after a storage or deployment change. + + Back up metadata and artifact files as one recovery set. Verify a restore before production use. - - Pin the production deployment to an immutable image digest. Verify the release as described in [supply-chain evidence](/docs/security/). + + Pin production containers to an immutable image digest. Verify the release with the [supply-chain guide](/docs/security/). -## Pick a data layer +## Choose a data layer -**Compact Compose** uses one SQLite database and one file volume. Run only one application process with this data layer. +**Compact Compose** uses one SQLite database and one file volume. Run one application process with this mode. -**External storage** uses PostgreSQL and object storage. Kubernetes, AWS, Google Cloud, and external-storage Compose use this data layer. Application processes can scale horizontally. +**External storage** uses PostgreSQL and object storage. Compose, Kubernetes, AWS, and Google Cloud support this data layer. -**Cloudflare** uses D1 and R2 through the Worker runtime, with the same product services as the other targets. +**Cloudflare** uses D1 and R2 through the Worker runtime. It uses the same product services as the other targets. -## After deploying +## Continue after deployment -- [Welcome your team](/docs/connect-agents/): the introduction for each teammate. -- [Connect agents to the MCP server](/docs/mcp/): one command for each client. -- [Review with coding agents](/docs/agents/): live feedback for Pi, OpenCode, and Claude Code. +- [Welcome your team](/docs/connect-agents/). +- [Connect agents to the MCP server](/docs/mcp/). +- [Review with coding agents](/docs/agents/). diff --git a/apps/site/src/content/docs/docs/deploy/kubernetes.mdx b/apps/site/src/content/docs/docs/deploy/kubernetes.mdx new file mode 100644 index 0000000..1f4af80 --- /dev/null +++ b/apps/site/src/content/docs/docs/deploy/kubernetes.mdx @@ -0,0 +1,338 @@ +--- +title: Deploy on Kubernetes +description: Install Artifact Server in an existing Kubernetes cluster with Helm, PostgreSQL, and S3-compatible object storage. +sidebar: + order: 3 +--- + +Use this guide to run Artifact Server in an existing Kubernetes cluster. The Helm chart installs only the application workload. + +You must supply PostgreSQL, S3-compatible object storage, DNS, TLS certificates, routing, secrets, and one browser identity provider. + + + +The chart source is in [`packaging/helm/artifact-server`](https://github.com/plannotator/artifact-server/tree/main/packaging/helm/artifact-server). A public chart package is not available yet. + +## Requirements + +| Requirement | Supported configuration | +| --- | --- | +| Kubernetes | Versions 1.34, 1.35, or 1.36 | +| Helm | Version 4.2.x | +| Application image | The released OCI image, pinned by SHA-256 digest | +| Database | An existing PostgreSQL database | +| Object storage | An existing S3-compatible bucket | +| Routing | An Ingress, Gateway, or provider edge that routes two hosts | +| TLS | One application certificate and one wildcard-content certificate | +| Browser authentication | WorkOS or one OpenID Connect (OIDC) provider | +| Secrets | One existing Kubernetes Secret in the release namespace | +| Recovery | Provider procedures for PostgreSQL and object storage | + +Run the commands in this guide from the repository root. Use a released image digest for a team installation. + +## Prepare the external services + +Create one PostgreSQL database for the installation. Create one S3-compatible bucket for the same installation. + +Use encrypted provider connections. Give Artifact Server access only to its database and bucket. + +When possible, use a dedicated bucket. A dedicated bucket makes complete backup and restore operations easier to define. + +Create these two public DNS names: + +- `artifacts.example.com` for the trusted application. +- `*.content.example.net` for isolated artifact versions. + +The two names must use separate registrable domains. The routing layer sends both names to the same private `ClusterIP` Service. + +## Create the runtime Secret + +Keep all credential values outside the repository and Helm values. The chart mounts Secret keys as read-only files. + +Create the bootstrap service key in `as_key_key__` form: + +```sh +install -d -m 0700 /secure/artifact-server +printf 'as_key_key_%s_%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 32)" \ + > /secure/artifact-server/api-token +chmod 0400 /secure/artifact-server/api-token +``` + +Place the PostgreSQL connection URL in `/secure/artifact-server/database-url`. Protect this file with mode `0400`. + +If you use static S3 credentials, place them in two files. When you use workload identity, skip these files. + +```text +/secure/artifact-server/s3-access-key-id +/secure/artifact-server/s3-secret-access-key +``` + +Create the namespace and Secret for static S3 credentials: + +```sh +kubectl create namespace artifact-server + +kubectl --namespace artifact-server create secret generic artifact-server-runtime \ + --from-file=api-token=/secure/artifact-server/api-token \ + --from-file=database-url=/secure/artifact-server/database-url \ + --from-file=s3-access-key-id=/secure/artifact-server/s3-access-key-id \ + --from-file=s3-secret-access-key=/secure/artifact-server/s3-secret-access-key +``` + +If you use workload identity, create the Secret without the two S3 key files: + +```sh +kubectl --namespace artifact-server create secret generic artifact-server-runtime \ + --from-file=api-token=/secure/artifact-server/api-token \ + --from-file=database-url=/secure/artifact-server/database-url +``` + +The first startup stores the bootstrap service key as a managed API key. Later startups require the exact same key. + +Protect the Secret with your cluster recovery system. A restored database will reject a different bootstrap key. + +## Configure browser authentication + +Each team installation requires exactly one browser identity provider. The chart rejects an incomplete provider configuration. + +### Generic OIDC + +Register this redirect URI with the provider: + +```text +https://artifacts.example.com/auth/callback +``` + +Use the exact issuer URL and client ID in the Helm values. The default scope string is `openid email profile`. + +If the client requires a secret, add the secret file to `artifact-server-runtime`. Then name its key with `secret.keys.oidcClientSecret`. + +### WorkOS + +Set `identity.workosClientId` and `identity.workosIssuer` in the Helm values. The issuer is the exact HTTPS AuthKit origin. + +Add the WorkOS API key to `artifact-server-runtime`. Then name its key with `secret.keys.workosApiKey`. + +Do not configure WorkOS and OIDC together. + +## Create the Helm values + +Create `artifact-server-values.yaml`. Replace every example value before installation. + +```yaml +replicaCount: 2 + +image: + repository: ghcr.io/plannotator/artifact-server + digest: sha256:REPLACE_WITH_THE_RELEASE_DIGEST + +configuration: + installationId: team-example + bootstrapAdministratorEmail: admin@example.com + applicationOrigin: https://artifacts.example.com + contentDomain: content.example.net + postgresConnectionBudget: 22 + postgresPoolSize: 10 + s3: + bucket: artifact-server-team-example + region: us-east-1 + +identity: + oidcClientId: artifact-server + oidcIssuer: https://idp.example.com/realms/main + +secret: + name: artifact-server-runtime + rolloutChecksum: secret-version-1 + keys: + s3AccessKeyId: s3-access-key-id + s3SecretAccessKey: s3-secret-access-key +``` + +The chart rejects a mutable image tag by default. `image.allowMutableTag` exists only for local chart development. + +The example connection budget covers two server pools, one migration connection, and one cleanup connection. + +Use this formula for another replica count: + +```text +(replicaCount × postgresPoolSize) + migration connection + cleanup connection ≤ postgresConnectionBudget +``` + +The chart rejects values that exceed the connection budget. + +## Configure workload identity + +When the workload uses its cloud identity, leave both S3 key names empty. + +Add the provider annotations to the service account: + +```yaml +secret: + keys: + s3AccessKeyId: "" + s3SecretAccessKey: "" + +serviceAccount: + annotations: + example.com/workload-identity: replace-with-provider-value + automountServiceAccountToken: false +``` + +Replace the example annotation with the annotation for your identity system. + +If the identity system requires the projected Kubernetes token, set `automountServiceAccountToken` to `true`. + +Otherwise, keep `automountServiceAccountToken` set to `false`. + +## Configure routing and TLS + +The chart creates a private `ClusterIP` Service. Ingress is disabled by default. + +If your controller uses `networking.k8s.io/v1`, enable the included Ingress: + +```yaml +ingress: + enabled: true + className: nginx + applicationTlsSecretName: artifact-server-application-tls + contentTlsSecretName: artifact-server-content-tls +``` + +The application TLS Secret must cover `artifacts.example.com`. The content TLS Secret must cover `*.content.example.net`. + +The included Ingress requires an HTTPS application origin without a path or explicit port. + +If you use Gateway API or a provider edge, leave `ingress.enabled` set to `false`. Route both hosts to the chart Service. + +## Install Artifact Server + +Install the chart with Helm: + +```sh +helm upgrade --install artifact-server ./packaging/helm/artifact-server \ + --namespace artifact-server \ + --values artifact-server-values.yaml \ + --rollback-on-failure \ + --wait +``` + +Helm runs `artifactserver migrate apply` before each installation and upgrade. A failed migration blocks the Deployment change. + +Successful migration Jobs are removed. Failed migration Jobs remain for inspection until the next Helm attempt. + +Inspect a failed migration: + +```sh +kubectl --namespace artifact-server logs job/artifact-server-migrate +``` + +Correct the provider or Secret failure. Then run the installation command again. + +## Verify the installation + +Verify the rollout and the internal readiness endpoint: + +```sh +kubectl --namespace artifact-server rollout status deployment/artifact-server +helm test artifact-server --namespace artifact-server --logs +kubectl --namespace artifact-server get pods,service,cronjob +``` + +The chart uses `/health` for startup and liveness probes. It uses `/ready` for readiness and the Helm test. + +Verify both public and account-required delivery through the final HTTPS hosts. Then verify browser login and the `/mcp` endpoint. + +Use the [publishing guide](/docs/publish/) and the [MCP guide](/docs/mcp/) to verify this product behavior. + +## Staging cleanup + +The chart creates a cleanup `CronJob` by default. It runs one bounded cleanup pass every 15 minutes. + +Serving Pods use the `external` cleanup schedule. They do not run a second cleanup loop. + +Change the schedule or limits under `cleanup` in the Helm values. + +If another operator runs this command, you can disable the `CronJob`: + +```sh +artifactserver maintenance cleanup-staging --once --mode external-storage +``` + +## Back up and restore + +The chart does not create or restore database and object backups. Use the recovery procedures for your PostgreSQL and object providers. + +Treat the database, the complete installation object set, and the runtime Secret as one recovery set. + +Before each upgrade, complete these actions: + +1. Stop write traffic at the trusted application edge. +2. Suspend the cleanup `CronJob`. +3. Scale the Artifact Server Deployment to zero replicas. +4. Create a transaction-consistent PostgreSQL backup. +5. Copy the complete installation object set. +6. Record the installation ID, image digest, provider backup IDs, and backup time. +7. Scale the Deployment to its configured replica count. +8. Resume the cleanup `CronJob`. +9. Restore traffic at the trusted application edge. + +Restore both providers into empty targets. Use the same installation ID and the exact runtime Secret from the recovery set. + +After restoration, install the same image digest. Then verify readiness, stable IDs, private access, and exact artifact bytes. + +## Upgrade and rollback + +Create and verify one recovery set before each upgrade. Then replace `image.digest` with the new release digest. + +Run the installation command again. The migration Job completes before the rolling Deployment change. + +The default Deployment uses two replicas, zero unavailable Pods, and one surge Pod during a rolling update. + +Verify the new release before you remove the recovery set. + +Helm rollback does not reverse a database migration. Verify that the old image supports the current schema before rollback. + +List the release revisions: + +```sh +helm history artifact-server --namespace artifact-server +``` + +Roll back to one compatible revision: + +```sh +helm rollback artifact-server --namespace artifact-server --wait +helm test artifact-server --namespace artifact-server --logs +``` + +## Uninstall + +Remove the Artifact Server workloads: + +```sh +helm uninstall artifact-server --namespace artifact-server --wait +``` + +This command does not remove the external Secret, PostgreSQL database, bucket, DNS records, or TLS certificates. + +Verify a complete recovery set before you remove any durable provider resource. + +## Current limits + +- The chart does not create a cluster, database, bucket, DNS zone, certificate, Ingress controller, or secret manager. +- The chart does not include a Horizontal Pod Autoscaler. +- The chart leaves `NetworkPolicy` disabled until the operator supplies exact rules for the cluster network. +- The kind gate uses `ClusterIP` and port forwarding. It does not verify an Ingress controller or wildcard TLS. +- The kind gate uses placeholder OIDC configuration. It does not complete browser authentication. +- The kind gate does not verify the scheduled execution of the cleanup `CronJob`. +- The kind gate preserves external providers through uninstall and reinstall. It does not perform a coordinated provider restore. +- The bounded kind read sample is a regression baseline. It is not a production capacity claim. + +Read the [chart README](https://github.com/plannotator/artifact-server/blob/main/packaging/helm/artifact-server/README.md), [`values.yaml`](https://github.com/plannotator/artifact-server/blob/main/packaging/helm/artifact-server/values.yaml), and the [runtime qualification test](https://github.com/plannotator/artifact-server/blob/main/tests/release/helm-chart.test.ts) for implementation details. diff --git a/apps/site/src/content/docs/docs/index.mdx b/apps/site/src/content/docs/docs/index.mdx index 6736253..37465ab 100644 --- a/apps/site/src/content/docs/docs/index.mdx +++ b/apps/site/src/content/docs/docs/index.mdx @@ -80,3 +80,7 @@ The [@plannotator/agent-bridge](https://github.com/plannotator/agent-bridge) pro - [Verify software supply-chain evidence](/docs/security/) - [Deploy for a team](/docs/deploy/) - [Deploy the Cloudflare package](/docs/deploy/cloudflare/) +- [Deploy with Compose](/docs/deploy/compose/) +- [Deploy on Kubernetes](/docs/deploy/kubernetes/) +- [Deploy on AWS](/docs/deploy/aws/) +- [Deploy on Google Cloud](/docs/deploy/google-cloud/) diff --git a/apps/site/src/pages/index.md.ts b/apps/site/src/pages/index.md.ts index 951bdf0..47bcf23 100644 --- a/apps/site/src/pages/index.md.ts +++ b/apps/site/src/pages/index.md.ts @@ -34,6 +34,10 @@ Open source under the GNU Affero General Public License v3.0 (AGPL-3.0-only): us - [Review with coding agents](https://artifactserver.com/docs/agents/index.md) - [Deploy for a team](https://artifactserver.com/docs/deploy/index.md) - [Cloudflare deployment](https://artifactserver.com/docs/deploy/cloudflare/index.md) +- [Compose deployment](https://artifactserver.com/docs/deploy/compose/index.md) +- [Kubernetes deployment](https://artifactserver.com/docs/deploy/kubernetes/index.md) +- [AWS deployment](https://artifactserver.com/docs/deploy/aws/index.md) +- [Google Cloud deployment](https://artifactserver.com/docs/deploy/google-cloud/index.md) - [Source](https://github.com/plannotator/artifact-server) `; diff --git a/packaging/compose/README.md b/packaging/compose/README.md index 9c701c3..21d7d64 100644 --- a/packaging/compose/README.md +++ b/packaging/compose/README.md @@ -193,11 +193,10 @@ recommended by this production package. ### Configure providers and secrets -Copy the external-storage environment example and create the two required -secret files: +Copy the shared environment example. Then create the two required secret files: ```sh -cp .env.external-storage.example .env +cp .env.example .env install -d -o 1000 -g 1000 -m 0700 /etc/artifact-server printf 'as_key_key_%s_%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 32)" \ > /etc/artifact-server/api-token diff --git a/project/spec/cloud-deployment-contract.md b/project/spec/cloud-deployment-contract.md index acca9fe..0f8e2fe 100644 --- a/project/spec/cloud-deployment-contract.md +++ b/project/spec/cloud-deployment-contract.md @@ -1,8 +1,8 @@ # Cloud deployment contract -Status: shared executable contract and AWS and GCP provider packages -implemented; every target remains gated by its own unfinished real-cloud release -qualification +Status: shared executable contract and AWS and GCP provider packages implemented. +The public AWS and GCP variants passed isolated live qualification. Signed +release evidence and the AWS private-ingress qualification remain open. This document is the handoff boundary for the Cloudflare, AWS, and GCP deployment work. It says exactly what every package receives, what it creates,