A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at main 490 lines 11 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#### Enable debug logging 247 248Toggle debug logging at runtime without restarting the container: 249 250```bash 251# Enable debug logging (auto-reverts after 30 minutes) 252docker kill -s SIGUSR1 atcr-appview 253docker kill -s SIGUSR1 atcr-hold 254 255# Manually disable before timeout 256docker kill -s SIGUSR1 atcr-appview 257``` 258 259When toggled, you'll see: 260``` 261level=INFO msg="Log level changed" from=INFO to=DEBUG trigger=SIGUSR1 auto_revert_in=30m0s 262``` 263 264**Note:** Despite the command name, `docker kill -s SIGUSR1` does NOT stop the container. It sends a user-defined signal that the application handles to toggle debug mode. 265 266#### Restart services 267 268```bash 269# Restart all 270systemctl restart atcr 271 272# Or use docker-compose 273cd /opt/atcr 274docker compose -f deploy/docker-compose.prod.yml restart 275``` 276 277#### Rebuild after code changes 278 279```bash 280/opt/atcr/rebuild.sh 281``` 282 283#### Update configuration 284 285```bash 286# Edit environment 287nano /opt/atcr/.env 288 289# Restart services 290systemctl restart atcr 291``` 292 293## Architecture Details 294 295### Service Communication 296 297``` 298Internet 299300Caddy (443) ───────────┐ 301 ├─→ atcr-appview:5000 (Registry API + Web UI) 302 └─→ atcr-hold:8080 (Presigned URL generator) 303304 UpCloud S3 (blobs.atcr.io) 305``` 306 307### Data Flow: Push 308 309``` 3101. docker push atcr.io/user/image:tag 3112. AppView ← Docker client (manifest + blob metadata) 3123. AppView → ATProto PDS (store manifest record) 3134. Hold ← Docker client (request presigned URL) 3145. Hold → UpCloud S3 API (generate presigned URL) 3156. Hold → Docker client (return presigned URL) 3167. UpCloud S3 ← Docker client (upload blob directly) 317``` 318 319### Data Flow: Pull 320 321``` 3221. docker pull atcr.io/user/image:tag 3232. AppView ← Docker client (get manifest) 3243. AppView → ATProto PDS (fetch manifest record) 3254. AppView → Docker client (return manifest with holdEndpoint) 3265. Hold ← Docker client (request presigned URL) 3276. Hold → UpCloud S3 API (generate presigned URL) 3287. Hold → Docker client (return presigned URL) 3298. UpCloud S3 ← Docker client (download blob directly) 330``` 331 332**Key insight:** The hold service only generates presigned URLs. Actual data transfer happens directly between Docker clients and S3, minimizing bandwidth costs. 333 334## Troubleshooting 335 336### Issue: "Cannot connect to registry" 337 338**Check DNS:** 339```bash 340dig atcr.io 341dig hold01.atcr.io 342``` 343 344**Check Caddy logs:** 345```bash 346docker logs atcr-caddy 347``` 348 349**Check firewall:** 350```bash 351firewall-cmd --list-all 352``` 353 354### Issue: "Certificate errors" 355 356**Verify DNS is propagated:** 357```bash 358curl -I https://atcr.io 359``` 360 361**Check Caddy is obtaining certificates:** 362```bash 363docker logs atcr-caddy | grep -i certificate 364``` 365 366**Common causes:** 367- DNS not propagated (wait 30 minutes) 368- Cloudflare proxy enabled (must be disabled) 369- Port 80/443 blocked by firewall 370 371### Issue: "Presigned URLs fail" 372 373**Check S3 endpoint configuration:** 374```bash 375docker exec atcr-hold env | grep S3 376``` 377 378**Verify custom domain CNAME:** 379```bash 380dig blobs.atcr.io CNAME 381``` 382 383**Test S3 connectivity:** 384```bash 385docker exec atcr-hold wget -O- https://blobs.atcr.io/ 386``` 387 388**Common causes:** 389- Cloudflare proxy enabled on blobs.atcr.io 390- S3_ENDPOINT misconfigured 391- AWS credentials invalid 392 393### Issue: "Hold registration fails" 394 395**Check hold owner DID:** 396```bash 397docker exec atcr-hold env | grep HOLD_OWNER 398``` 399 400**Verify OAuth flow:** 401```bash 402/opt/atcr/get-hold-oauth.sh 403``` 404 405**Manual registration:** 406```bash 407# Get fresh OAuth URL 408docker restart atcr-hold 409docker logs -f atcr-hold 410``` 411 412### Issue: "High bandwidth usage" 413 414Presigned URLs should eliminate hold bandwidth. If seeing high usage: 415 416**Verify presigned URLs are enabled:** 417```bash 418docker logs atcr-hold | grep -i presigned 419``` 420 421**Check S3 configuration:** 422```bash 423docker exec atcr-hold env | grep S3_BUCKET 424# Should show your S3 bucket name 425``` 426 427**Verify direct S3 access:** 428```bash 429# Push should show 307 redirects in logs 430docker logs -f atcr-hold 431# Then push an image 432``` 433 434### Automatic Updates 435 436```bash 437# Install automatic updates 438dnf install -y dnf-automatic 439 440# Enable timer 441systemctl enable --now dnf-automatic.timer 442``` 443 444### Monitoring 445 446```bash 447# Install monitoring tools 448dnf install -y htop iotop nethogs 449 450# Monitor resources 451htop 452 453# Monitor Docker 454docker stats 455``` 456 457### Backups 458 459Critical data to backup: 460- `/opt/atcr/.env` - Configuration 461- Docker volumes: 462 - `atcr-appview-data` - Auth keys, UI database, OAuth tokens 463 - `caddy_data` - TLS certificates 464 465```bash 466# Backup volumes 467docker run --rm \ 468 -v atcr-appview-data:/data \ 469 -v /backup:/backup \ 470 alpine tar czf /backup/atcr-appview-data.tar.gz /data 471``` 472 473## Scaling Considerations 474 475### Single Server (Current Setup) 476- Suitable for: 100-1000 users 477- Bottleneck: AppView CPU (manifest queries) 478- Storage: Unlimited (S3) 479 480### Multi-Server (Future) 481- Multiple AppView instances behind load balancer 482- Shared Redis for hold cache (replace in-memory cache) 483- PostgreSQL for UI database (replace SQLite) 484- Multiple hold services (geo-distributed) 485 486## Support 487 488- Documentation: https://tangled.org/evan.jarrett.net/at-container-registry 489- Issues: https://tangled.org/evan.jarrett.net/at-container-registry/issues 490- Bluesky: @evan.jarrett.net