Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sops.nix
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ let
"tau"
];
"modules/buildbot/secrets.yaml" = [ "psi" ];
"modules/container-registry/secrets.yaml" = [ "eta" ];
"modules/container-registry/secrets.yaml" = [ "psi" ];
"modules/monitoring/secrets.yaml" = [ "rho" ];
"modules/n8n/secrets.yaml" = [ "tau" ];
"terraform/authentik/neko-secrets.yaml" = [ "rho" ];
Expand Down
2 changes: 1 addition & 1 deletion .sops.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ creation_rules:
path_regex: modules/buildbot/secrets.yaml
- key_groups:
- age:
- age1zdhqm6ptcnuu3tf2lzcngqmf6eud7jfah7v8falfy5mdksmnfuzq35sq54
- age18yuzugm0f2l7cc4sdmp5f442af2w897x9l9syeaef3yx3jzc5pjs9zez0f
- age1730f3cxdyh56zw8xcvlmpa7u2x7353wu4u0e58kyx24rsefgp98sxehm6s
- age15h90xndr3kn5d8et69y5hf54lclmvafhk5a0d3hgc0a9fvjexcaq0t9hvr
path_regex: modules/container-registry/secrets.yaml
Expand Down
2 changes: 1 addition & 1 deletion docs/admin/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Nix가 provider 버전의 단일 기준입니다. 실행 cache는 unit별 `.terr
| Unit | 책임 |
|------|------|
| `dns` | 공개 ingress가 필요한 Cloudflare DNS. 내부 서비스 이름은 Headscale split DNS |
| `r2` | 서비스 bucket·custom domain. niks3, OCI Registry, Stalwart blobs, eta Restic 등 |
| `r2` | 서비스 bucket·custom domain. niks3, Stalwart blobs, eta Restic 등 |
| `authentik` | 사용자·그룹, proxy/OIDC provider, application, outpost attachment, policy·claim mapping |
| `headscale` | Authentik과 같은 사용자 inventory에서 생성한 database ACL policy |
| `healthchecksio` | dead-man checks와 기존 Slack integration 연결 |
Expand Down
2 changes: 1 addition & 1 deletion hosts/eta.nix
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ in
./eta/restic
../modules/buildbot/edge-proxy.nix
../modules/gitea-mq
../modules/container-registry
../modules/container-registry/edge-proxy.nix
../modules/nginx/edge.nix
../modules/niks3/edge-proxy.nix
../modules/uptermd
Expand Down
1 change: 1 addition & 0 deletions hosts/psi.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ in
../modules/monitoring/exporters/systemd-status.nix
../modules/backup/borg.nix
../modules/niks3
../modules/container-registry
../modules/vllm
../modules/biodb
../modules/biodb/databases.nix
Expand Down
86 changes: 57 additions & 29 deletions modules/container-registry/README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,64 @@
# Container Registry

`registry.sjanglab.org` is a public-pull OCI Distribution registry backed by the
private Cloudflare R2 `container-registry` bucket.
`registry.sjanglab.org` is a private zot OCI registry. zot runs on psi and
stores images on the `/data` HDD array. eta terminates public TLS and proxies to
psi over wg-admin; on psi the name resolves to the local nginx vhost, so k3s,
docker and skopeo pull from local disk without leaving the host.

## Access model
## Repository layout

- `research/<project>/<image>:<version>` for project images.
- `research/base/<image>:<version>` for shared runtimes such as PyTorch.
- `infra/<image>:<version>` for service and CI images.

- Pulls are anonymous.
- `docker-push-bot` may pull and push any repository.
- `registry-admin` may pull, push, and delete any repository. This account is
reserved for manual retention and recovery work.
- HTTP deletion remains enabled so `registry-admin` can remove manifests and
offline garbage collection can remove the resulting unreferenced blobs.
- Tagged manifests and referenced layers have no age-based retention policy.
Other paths are reserved for administrators. CI keeps its BuildKit cache next to
each image as `<image>-cache:buildkit`.

Only publish redistributable build artifacts. Images must not contain model
parameters, research data, internal source code, credentials, or software whose
license prohibits redistribution.

## Health image
## Access model

Every request needs a login; there is no anonymous pull.

Use the data-only OCI health image for anonymous pull checks:
| Identity | `research/**` | `infra/**` |
| --- | --- | --- |
| `sjanglab-admins`, `registry-admin` | read, push, overwrite, delete | same |
| `docker-push-bot` | read, push, overwrite tags | same |
| `sjanglab-researchers` | read, push new tags | none |
| `sjanglab-students` | read | none |

```text
registry.sjanglab.org/sjanglab/registry-healthcheck@sha256:1634c441a4f34568a2c451544653bee64c85cee0030a778be1a8a9612233729c
Researchers cannot overwrite a published tag, so a version always names the
same image. `registry-admin` is a break-glass account for recovery when
authentik is unavailable. There is no retention policy; images stay until an
administrator deletes them.

## Member login

Sign in to <https://registry.sjanglab.org> with authentik. docker and skopeo
cannot use OIDC, so create an API key in the web UI with a 30-day expiry and
use it as the password:

```bash
docker login registry.sjanglab.org --username <slack email> --password-stdin
```

Tag `v1` points to this digest. The image contains only `README.txt` and may be
used for anonymous pull checks.
The username is the authentik account name, which is the Slack email.

An API key keeps the groups its owner had at their last web login. Removing a
member from authentik does not revoke keys they already created, and only the
owner can delete a key, so keep keys short-lived.

## Image format

zot accepts both OCI and Docker manifests and stores them unchanged. Prefer OCI
for new pipelines because it allows zstd layers and manifest annotations:

```bash
skopeo copy --format oci docker-archive:./image.tar docker://registry.sjanglab.org/research/project/name:tag
docker buildx build --output type=image,name=registry.sjanglab.org/research/project/name:tag,push=true,oci-mediatypes=true .
```

## CI login

Expand Down Expand Up @@ -59,26 +91,22 @@ sops -d --extract '["container-registry-admin-password"]' \
docker login registry.sjanglab.org --username registry-admin --password-stdin
```

Use this account only for manual retention and recovery work. Deleting a
manifest makes its exclusive blobs eligible for the next offline garbage
collection pass.
The same credentials log in to the web UI, where tags can also be deleted.
zot garbage-collects blobs that are no longer referenced while it keeps
serving requests.

## Credential rotation

1. Generate a random password and bcrypt cost-12 hash without putting either in
process arguments.
2. Replace both password and password-hash keys for the account being rotated
in `secrets.yaml`.
3. Deploy eta. `sops-nix` restarts `docker-auth.service` after rendering the new
hash.
3. Deploy psi. `sops-nix` restarts `zot.service` after rendering the new
htpasswd file.
4. Replace the CI secret and verify login before removing old runner state.

## Storage maintenance

The daily garbage-collection unit stops Distribution before its mark-and-sweep
pass, then starts it again. This prevents concurrent uploads from being mistaken
for unreachable data. Garbage collection does not delete tagged images.
## Storage

R2 credentials must remain Object Read & Write scoped to only the
`container-registry` bucket. Large blob pulls receive signed R2 redirects;
manifest and token traffic continues through eta.
Blobs, the dedupe index and metaDB live in `/data/zot`. Repeated blobs are
hardlinked, so each layer is stored once. The store is not backed up; if it is
lost, let CI push all images again.
123 changes: 0 additions & 123 deletions modules/container-registry/auth.nix

This file was deleted.

Loading
Loading