A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at loom 470 lines 10 kB view raw view rendered
1# ATCR UpCloud Deployment Guide 2 3This guide walks you through deploying ATCR on UpCloud with Rocky Linux. 4 5## Architecture 6 7- **AppView** (atcr.io) - OCI registry API + web UI 8- **Hold Service** (hold01.atcr.io) - Presigned URL generator for blob storage 9- **Caddy** - Reverse proxy with automatic HTTPS 10- **UpCloud Object Storage** (blobs.atcr.io) - S3-compatible blob storage 11 12## Prerequisites 13 14### 1. UpCloud Account 15- Active UpCloud account 16- Object Storage enabled 17- Billing configured 18 19### 2. Domain Names 20You need three DNS records: 21- `atcr.io` (or your domain) - AppView 22- `hold01.atcr.io` - Hold service 23- `blobs.atcr.io` - S3 storage (CNAME) 24 25### 3. ATProto Account 26- Bluesky/ATProto account 27- Your DID (get from: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social`) 28 29### 4. UpCloud Object Storage Bucket 30Create an S3 bucket in UpCloud Object Storage: 311. Go to UpCloud Console → Storage → Object Storage 322. Create new bucket (e.g., `atcr-blobs`) 333. Note the region (e.g., `us-chi1`) 344. Generate access credentials (Access Key ID + Secret) 355. Note the endpoint (e.g., `s3.us-chi1.upcloudobjects.com`) 36 37## Deployment Steps 38 39### Step 1: Configure DNS 40 41Set up DNS records (using Cloudflare or your DNS provider): 42 43``` 44Type Name Value Proxy 45──────────────────────────────────────────────────────────────────────────── 46A atcr.io [your-upcloud-ip] ☁️ DISABLED 47A hold01.atcr.io [your-upcloud-ip] ☁️ DISABLED 48CNAME blobs.atcr.io atcr-blobs.us-chi1.upcloudobjects.com ☁️ DISABLED 49``` 50 51**IMPORTANT:** 52- **DISABLE Cloudflare proxy** (gray cloud, not orange) for all three domains 53- Proxied connections break Docker registry protocol and presigned URLs 54- You'll still get HTTPS via Caddy's Let's Encrypt integration 55 56Wait for DNS propagation (5-30 minutes). Verify with: 57```bash 58dig atcr.io 59dig hold01.atcr.io 60dig blobs.atcr.io 61``` 62 63### Step 2: Create UpCloud Server 64 651. Go to UpCloud Console → Servers → Deploy a new server 662. Select location (match your S3 region if possible) 673. Select **Rocky Linux 9** operating system 684. Choose plan (minimum: 2 GB RAM, 1 CPU) 695. Configure hostname: `atcr` 706. Enable IPv4 public networking 717. **Optional:** Enable IPv6 728. **User data:** Paste contents of `deploy/init-upcloud.sh` 73 - Update `ATCR_REPO` variable with your git repository URL 74 - Or leave empty and manually copy files later 759. Create SSH key or use password authentication 7610. Click **Deploy** 77 78### Step 3: Wait for Initialization 79 80The init script will: 81- Update system packages (~2-5 minutes) 82- Install Docker and Docker Compose 83- Configure firewall 84- Clone repository (if ATCR_REPO configured) 85- Create systemd service 86- Create helper scripts 87 88Monitor progress: 89```bash 90# SSH into server 91ssh root@[your-upcloud-ip] 92 93# Check cloud-init logs 94tail -f /var/log/cloud-init-output.log 95``` 96 97Wait for the completion message in the logs. 98 99### Step 4: Configure Environment 100 101Edit the environment configuration: 102 103```bash 104# SSH into server 105ssh root@[your-upcloud-ip] 106 107# Edit environment file 108cd /opt/atcr 109nano .env 110``` 111 112**Required configuration:** 113 114```bash 115# Domains 116APPVIEW_DOMAIN=atcr.io 117HOLD_DOMAIN=hold01.atcr.io 118 119# Your ATProto DID 120HOLD_OWNER=did:plc:your-did-here 121 122# UpCloud S3 credentials 123AWS_ACCESS_KEY_ID=your-access-key-id 124AWS_SECRET_ACCESS_KEY=your-secret-access-key 125AWS_REGION=us-chi1 126S3_BUCKET=atcr-blobs 127 128# S3 endpoint (choose one): 129# Option 1: Custom domain (recommended) 130S3_ENDPOINT=https://blobs.atcr.io 131# Option 2: Direct UpCloud endpoint 132# S3_ENDPOINT=https://s3.us-chi1.upcloudobjects.com 133 134# Public access (optional) 135HOLD_PUBLIC=false # Set to true to allow anonymous pulls 136``` 137 138Save and exit (Ctrl+X, Y, Enter). 139 140### Step 5: Start ATCR 141 142```bash 143# Start services 144systemctl start atcr 145 146# Check status 147systemctl status atcr 148 149# Verify containers are running 150docker ps 151``` 152 153You should see three containers: 154- `atcr-caddy` 155- `atcr-appview` 156- `atcr-hold` 157 158### Step 6: Complete Hold OAuth Registration 159 160The hold service needs to register itself with your PDS: 161 162```bash 163# Get OAuth URL from logs 164/opt/atcr/get-hold-oauth.sh 165``` 166 167Look for output like: 168``` 169Visit this URL to authorize: https://bsky.social/oauth/authorize?... 170``` 171 1721. Copy the URL and open in your browser 1732. Log in with your ATProto account 1743. Authorize the hold service 1754. Return to terminal 176 177The hold service will create records in your PDS: 178- `io.atcr.hold` - Hold definition 179- `io.atcr.hold.crew` - Your membership as captain 180 181Verify registration: 182```bash 183docker logs atcr-hold | grep -i "success\|registered\|created" 184``` 185 186### Step 7: Test the Registry 187 188#### Test 1: Check endpoints 189 190```bash 191# AppView (should return {}) 192curl https://atcr.io/v2/ 193 194# Hold service (should return {"status":"ok"}) 195curl https://hold01.atcr.io/health 196``` 197 198#### Test 2: Configure Docker client 199 200On your local machine: 201 202```bash 203# Install credential helper 204# (Build from source or download release) 205go install atcr.io/cmd/docker-credential-atcr@latest 206 207# Configure Docker to use the credential helper 208# Add to ~/.docker/config.json: 209{ 210 "credHelpers": { 211 "atcr.io": "atcr" 212 } 213} 214``` 215 216#### Test 3: Push a test image 217 218```bash 219# Tag an image 220docker tag alpine:latest atcr.io/yourhandle/test:latest 221 222# Push to ATCR 223docker push atcr.io/yourhandle/test:latest 224 225# Pull from ATCR 226docker pull atcr.io/yourhandle/test:latest 227``` 228 229### Step 8: Monitor and Maintain 230 231#### View logs 232 233```bash 234# All services 235/opt/atcr/logs.sh 236 237# Specific service 238/opt/atcr/logs.sh atcr-appview 239/opt/atcr/logs.sh atcr-hold 240/opt/atcr/logs.sh atcr-caddy 241 242# Or use docker directly 243docker logs -f atcr-appview 244``` 245 246#### Restart services 247 248```bash 249# Restart all 250systemctl restart atcr 251 252# Or use docker-compose 253cd /opt/atcr 254docker compose -f deploy/docker-compose.prod.yml restart 255``` 256 257#### Rebuild after code changes 258 259```bash 260/opt/atcr/rebuild.sh 261``` 262 263#### Update configuration 264 265```bash 266# Edit environment 267nano /opt/atcr/.env 268 269# Restart services 270systemctl restart atcr 271``` 272 273## Architecture Details 274 275### Service Communication 276 277``` 278Internet 279280Caddy (443) ───────────┐ 281 ├─→ atcr-appview:5000 (Registry API + Web UI) 282 └─→ atcr-hold:8080 (Presigned URL generator) 283284 UpCloud S3 (blobs.atcr.io) 285``` 286 287### Data Flow: Push 288 289``` 2901. docker push atcr.io/user/image:tag 2912. AppView ← Docker client (manifest + blob metadata) 2923. AppView → ATProto PDS (store manifest record) 2934. Hold ← Docker client (request presigned URL) 2945. Hold → UpCloud S3 API (generate presigned URL) 2956. Hold → Docker client (return presigned URL) 2967. UpCloud S3 ← Docker client (upload blob directly) 297``` 298 299### Data Flow: Pull 300 301``` 3021. docker pull atcr.io/user/image:tag 3032. AppView ← Docker client (get manifest) 3043. AppView → ATProto PDS (fetch manifest record) 3054. AppView → Docker client (return manifest with holdEndpoint) 3065. Hold ← Docker client (request presigned URL) 3076. Hold → UpCloud S3 API (generate presigned URL) 3087. Hold → Docker client (return presigned URL) 3098. UpCloud S3 ← Docker client (download blob directly) 310``` 311 312**Key insight:** The hold service only generates presigned URLs. Actual data transfer happens directly between Docker clients and S3, minimizing bandwidth costs. 313 314## Troubleshooting 315 316### Issue: "Cannot connect to registry" 317 318**Check DNS:** 319```bash 320dig atcr.io 321dig hold01.atcr.io 322``` 323 324**Check Caddy logs:** 325```bash 326docker logs atcr-caddy 327``` 328 329**Check firewall:** 330```bash 331firewall-cmd --list-all 332``` 333 334### Issue: "Certificate errors" 335 336**Verify DNS is propagated:** 337```bash 338curl -I https://atcr.io 339``` 340 341**Check Caddy is obtaining certificates:** 342```bash 343docker logs atcr-caddy | grep -i certificate 344``` 345 346**Common causes:** 347- DNS not propagated (wait 30 minutes) 348- Cloudflare proxy enabled (must be disabled) 349- Port 80/443 blocked by firewall 350 351### Issue: "Presigned URLs fail" 352 353**Check S3 endpoint configuration:** 354```bash 355docker exec atcr-hold env | grep S3 356``` 357 358**Verify custom domain CNAME:** 359```bash 360dig blobs.atcr.io CNAME 361``` 362 363**Test S3 connectivity:** 364```bash 365docker exec atcr-hold wget -O- https://blobs.atcr.io/ 366``` 367 368**Common causes:** 369- Cloudflare proxy enabled on blobs.atcr.io 370- S3_ENDPOINT misconfigured 371- AWS credentials invalid 372 373### Issue: "Hold registration fails" 374 375**Check hold owner DID:** 376```bash 377docker exec atcr-hold env | grep HOLD_OWNER 378``` 379 380**Verify OAuth flow:** 381```bash 382/opt/atcr/get-hold-oauth.sh 383``` 384 385**Manual registration:** 386```bash 387# Get fresh OAuth URL 388docker restart atcr-hold 389docker logs -f atcr-hold 390``` 391 392### Issue: "High bandwidth usage" 393 394Presigned URLs should eliminate hold bandwidth. If seeing high usage: 395 396**Verify presigned URLs are enabled:** 397```bash 398docker logs atcr-hold | grep -i presigned 399``` 400 401**Check S3 driver:** 402```bash 403docker exec atcr-hold env | grep STORAGE_DRIVER 404# Should be: s3 (not filesystem) 405``` 406 407**Verify direct S3 access:** 408```bash 409# Push should show 307 redirects in logs 410docker logs -f atcr-hold 411# Then push an image 412``` 413 414### Automatic Updates 415 416```bash 417# Install automatic updates 418dnf install -y dnf-automatic 419 420# Enable timer 421systemctl enable --now dnf-automatic.timer 422``` 423 424### Monitoring 425 426```bash 427# Install monitoring tools 428dnf install -y htop iotop nethogs 429 430# Monitor resources 431htop 432 433# Monitor Docker 434docker stats 435``` 436 437### Backups 438 439Critical data to backup: 440- `/opt/atcr/.env` - Configuration 441- Docker volumes: 442 - `atcr-appview-data` - Auth keys, UI database, OAuth tokens 443 - `caddy_data` - TLS certificates 444 445```bash 446# Backup volumes 447docker run --rm \ 448 -v atcr-appview-data:/data \ 449 -v /backup:/backup \ 450 alpine tar czf /backup/atcr-appview-data.tar.gz /data 451``` 452 453## Scaling Considerations 454 455### Single Server (Current Setup) 456- Suitable for: 100-1000 users 457- Bottleneck: AppView CPU (manifest queries) 458- Storage: Unlimited (S3) 459 460### Multi-Server (Future) 461- Multiple AppView instances behind load balancer 462- Shared Redis for hold cache (replace in-memory cache) 463- PostgreSQL for UI database (replace SQLite) 464- Multiple hold services (geo-distributed) 465 466## Support 467 468- Documentation: https://tangled.org/@evan.jarrett.net/at-container-registry 469- Issues: https://tangled.org/@evan.jarrett.net/at-container-registry/issues 470- Bluesky: @evan.jarrett.net