Try an installation with LocalStack
Install and remove a non-persistent test server
What you will do
Create a Compose installation using LocalStack on a Linux test server. This is a non-persistent test path: objects can disappear when the host or Docker restarts.
Before you begin
| Item | Requirement |
|---|---|
| Operating system | Linux, both x86_64 and arm64 |
| Docker Engine | 28 or later |
| Docker Compose | 2.24.6 or later |
| Disk | Docker data-root on XFS mounted with prjquota. preflight does not check this. Otherwise the scheduler quota check fails during deploy and installation stops. Complete XFS disk preparation first on ext4 servers. Docker Desktop is unsupported |
| Time | NTP synchronized. Docker starts automatically after reboot |
| Tools | git, jq, curl, openssl, Bun 1.3.14, Docker Buildx plugin, and an account with Docker daemon access |
| External access | Download images from ghcr.io and Docker Hub without signing in. Sessions connect to api.anthropic.com and github.com |
A repository checkout is required because Compose files and scripts/test-ops.sh are read from it. Images are not built from the checkout.
Complete XFS preparation on a Linux server separate from production data. Choose a test-only project, installation id, ports, and state/secret directories. test-ops defaults to project werft-test-ops; override it with TEST_OPS_PROJECT. If the default paths below belong to an existing installation, choose different paths and TEST_OPS_* values.
Steps
1. Images and source
TAG='<published-image-tag>'
SOURCE_REF='<matching-source-commit-or-release-tag>'
for app in control-host worker egress-proxy; do
echo "$app $(docker buildx imagetools inspect "ghcr.io/jeongjaesoon/werft-$app:$TAG" --format '{{.Manifest.Digest}}')"
donegit clone https://github.com/JeongJaeSoon/werft.git
cd werft
git checkout "$SOURCE_REF"
bun install --frozen-lockfileConfirm all three images return digests and that you checked out the matching source commit.
2. Test configuration
Keep secrets only in an env file outside the repository, readable only by operators. The script rejects group or other permissions.
sudo mkdir -p /etc/werft
sudo install -m 600 -o "$USER" infra/test-ops.env.example /etc/werft/test-ops.env
sudo install -d -m 700 -o "$USER" /var/lib/werft/test-opsOpen /etc/werft/test-ops.env in an editor and fill in the following. Tools such as sed -i create temporary files and fail with Permission denied in /etc/werft; use an editor that writes in place.
| Variable | Value |
|---|---|
POSTGRES_PASSWORD | Random value using only letters, digits, ., _, ~, and - |
EGRESS_AUTHORIZER_TOKEN | Random value of at least 32 characters |
PLATFORM_SECRET_KEY | 64-character hex from openssl rand -hex 32. Losing it prevents reading stored credentials |
BOOTSTRAP_TOKEN | Uncomment and enter a random 32–256-character token for creating the first owner. If omitted, the API generates one and scripts/test-ops.sh bootstrap-token shows it once after deploy |
Use EXECUTION_INSTALLATION_ID=test-ops, PLATFORM_SETTINGS_DOCUMENT=/etc/werft/settings.json, and EXECUTION_WORKSPACE_QUOTA=on. Set the credential allowlist to api.anthropic.com:443,github.com:443 and the private credential allowlist to localstack:4566. Leave AWS key, region, and bucket variables empty as in the example. LocalStack uses fixed test keys and the claude-sessions bucket.
Put the settings document in /etc/werft/settings.json and list the GitHub repositories sessions will use. It contains no secrets. Replace only the repository URL with your actual value. This example assumes a public repository. Each repository entry must contain "auth": { "kind": "github_app" }; otherwise import omits it and preflight blocks installation. For private repository authentication, see the API server documentation.
The migrate container must read this file, so use 0644. Create an empty file first, then fill it with the following content using an editor.
sudo install -m 644 -o "$USER" /dev/null /etc/werft/settings.json{
"version": 1,
"connections": {},
"repositories": {
"sample-app": {
"url": "https://github.com/<owner>/<repo>.git",
"branch": "main",
"auth": { "kind": "github_app" }
}
},
"mcp_servers": {}
}3. Release input and installation
Generate the release as in single-server step 3. Write it outside the checkout at /var/lib/werft/test-ops/release.json. Do not run the S3 preflight at this stage; use:
TEST_OPS_OBJECT_STORE=localstack scripts/test-ops.sh preflight /var/lib/werft/test-ops/release.json
TEST_OPS_OBJECT_STORE=localstack scripts/test-ops.sh deploy /var/lib/werft/test-ops/release.jsonThe quota check and service health must succeed. Add the model key through the console after installation.
Verify
Check scripts/test-ops.sh status, the ready response body and 200, and docs 200. Verify authentication under First owner and keys. This trial does not prove production AWS S3 permissions or restoration with data.
Troubleshooting
If the first deploy fails, started containers remain and stop is unavailable because current.json does not exist. Inspect failure logs for quota, network, or Secret issues. Confirm this installation's project name and bring it down as follows, without -v.
PROJECT='<this-installation-project>'
printf '%s\n' '{"services":{"x":{"image":"alpine:3"}},"networks":{"default":{}}}' \
| docker compose --project-name "$PROJECT" -f - down --remove-orphansAfter fixing the cause, rerun deploy with the same release file and environment. Use start to restart an installation that has already succeeded.
The first upgrade changing restart policies may recreate LocalStack and lose objects. Postpone the upgrade unless status confirms 0 uncollected checkpoints. If API startup failure coincides with pending.json, reseed is also rejected; reset discards all data. backup --stop does not remove this block.
LocalStack loses in-memory objects when its container is recreated. The API refuses to start if the database retains uncollected checkpoints but the bucket is empty. For automatic and manual start conditions, see the Compose lifecycle.
Before a planned restart, back up and stop writers.
# Back up, leave the writers stopped, restart the host, then restore the objects
scripts/test-ops.sh backup --stop
sudo reboot
scripts/test-ops.sh reseed /var/lib/werft/test-ops/backups/backup-<timestamp>This sequence and rebooting with checkpoints have not been verified by execution. reseed checks the source project/bucket/checksum, an empty bucket, and absence of checkpoint changes since the last backup. It rejects unmet conditions. After object loss, with API startup failure and a pending upgrade, reseed is rejected. Even a backup made with backup --stop does not remove that block. Choosing reset in this state discards the database, sessions, and checkpoints. Do not use it as a routine reboot recovery command.
Roll back
Follow Stop and remove Compose. For a successful installation, scripts/test-ops.sh stop brings down this project and retains volumes. For a failed first deploy, use the explicit-project cleanup with the Compose model on stdin above. A checkpoint-free trial does not authorize cleaning production buckets or other installations. reset is a separate choice requiring a deletion-scope check.
Next steps
Production single-server installation, Backup and restoration.