Container Registries
Rise generates temporary credentials for pushing container images to registries. The backend acts as a credential broker, abstracting provider-specific authentication.
Supported Providers
AWS ECR
Amazon Elastic Container Registry with scoped credentials via STS AssumeRole.
Configuration:
[registry]
type = "ecr"
region = "us-east-1"
account_id = "123456789012"
repo_prefix = "rise/"
role_arn = "arn:aws:iam::123456789012:role/rise-backend"
push_role_arn = "arn:aws:iam::123456789012:role/rise-backend-ecr-push"
auto_remove = false # Tag as orphaned instead of deleting
How it works:
- Backend assumes
push_role_arnwith inline session policy scoped to specific project - Backend calls AWS
GetAuthorizationTokenAPI with scoped credentials - Returns credentials valid for 12 hours, scoped to single project repository
- CLI uses credentials to push images
Image path: {account}.dkr.ecr.{region}.amazonaws.com/{repo_prefix}{project}:{tag}
Example: 123456789012.dkr.ecr.us-east-1.amazonaws.com/rise/my-app:latest
Docker Registry
Works with any Docker-compatible registry (Docker Hub, Harbor, Quay, local registries).
Configuration:
[registry]
type = "oci-client-auth"
registry_url = "localhost:5000"
namespace = "rise-apps"
How it works:
- Backend returns registry URL to CLI
- CLI uses existing Docker credentials from
~/.docker/config.json - No credential generation - relies on pre-authentication via
docker login
Common use cases:
- Local development: docker-compose registry (port 5000)
- Docker Hub:
registry_url = "docker.io",namespace = "myorg" - Harbor:
registry_url = "harbor.company.com",namespace = "project"
Local Development Registry
For local development, Rise includes a Docker registry in docker-compose:
registry:
image: registry:2
ports:
- "5000:5000"
volumes:
- registry_data:/var/lib/registry
Start:
mise backend:deps # Starts all services including registry
Access:
- Registry API: http://localhost:5000
- Registry UI: http://localhost:5001 (browse images)
Usage:
# List repositories
curl http://localhost:5000/v2/_catalog
# List tags
curl http://localhost:5000/v2/my-app/tags/list
# Deploy (automatically uses local registry)
rise deployment create my-app
⚠️ Production Warning: Local registry uses HTTP, has no auth, and uses Docker volumes. For production, use AWS ECR, GCR, or similar.
AWS ECR Production Setup
Architecture: Two-Role Pattern
Controller Role (rise-backend):
- Create/delete ECR repositories
- Tag repositories (managed, orphaned)
- Configure repository settings
- Assume the push role
Push Role (rise-backend-ecr-push):
- Push/pull images to ECR (under
rise/*prefix) - Used by backend to generate scoped credentials for CLI
Why two roles?
- Separation: Controller manages infrastructure, push handles images
- Least privilege: Scoped credentials limited to single repository
- Temporary: 12-hour max lifetime, can’t delete repositories
Terraform Module
Use the provided modules/rise-aws module:
module "rise_ecr" {
source = "../modules/rise-aws"
name = "rise-backend"
repo_prefix = "rise/"
auto_remove = false
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
output "rise_ecr_config" {
value = module.rise_ecr.rise_config
}
Apply:
cd terraform
terraform init
terraform apply
terraform output rise_ecr_config
With EKS + IRSA
Configure module for IRSA:
module "rise_ecr" {
source = "../modules/rise-aws"
name = "rise-backend"
repo_prefix = "rise/"
irsa_oidc_provider_arn = module.eks.oidc_provider_arn
irsa_namespace = "rise-system"
irsa_service_account = "rise-backend"
}
Helm values:
serviceAccount:
create: true
iamRoleArn: "arn:aws:iam::123456789012:role/rise-backend"
config:
registry:
type: "ecr"
region: "us-east-1"
account_id: "123456789012"
repo_prefix: "rise/"
role_arn: "arn:aws:iam::123456789012:role/rise-backend"
push_role_arn: "arn:aws:iam::123456789012:role/rise-backend-ecr-push"
# NO static credentials with IRSA
With IAM User (Non-AWS)
For running Rise outside AWS:
module "rise_ecr" {
source = "../modules/rise-aws"
name = "rise-backend"
repo_prefix = "rise/"
create_iam_role = false
create_iam_user = true
}
# Store credentials securely
resource "aws_secretsmanager_secret_version" "rise_ecr_creds" {
secret_id = aws_secretsmanager_secret.rise_ecr_creds.id
secret_string = jsonencode({
access_key_id = module.rise_ecr.access_key_id
secret_access_key = module.rise_ecr.secret_access_key
})
}
Configuration
Registry configuration is in config/ directory. See the registry examples at the top of this document for TOML configuration format.
Configuration file precedence (highest to lowest):
local.yaml(not checked into git, for local overrides){RISE_CONFIG_RUN_MODE}.yaml(e.g.,production.yaml,development.yaml)default.yaml
Environment variable substitution: You can reference environment variables in config files using ${VAR_NAME} or ${VAR_NAME:-default} syntax.
API Endpoint
Request credentials for a project:
GET /registry/credentials?project=my-app
Authorization: Bearer <jwt-token>
Response:
{
"credentials": {
"registry_url": "123456.dkr.ecr.us-east-1.amazonaws.com",
"username": "AWS",
"password": "eyJwYXlsb2FkIjoiS...",
"expires_in": 43200
},
"repository": "my-app"
}
Security
ECR Credential Scope:
- Scoped to specific project using STS AssumeRole with inline session policies
- Credentials for
my-appcan only push to{repo_prefix}my-app* - 12-hour lifespan (AWS enforced)
Docker:
- No credential scoping
- Use registry-specific access controls
Best Practices:
- Use IAM roles (ECR): Avoid static credentials
- Enable HTTPS: Always use TLS in production
- Monitor access: Track credential requests and usage
- Rotate credentials: For Docker registries, rotate regularly
- Least privilege: Scope credentials to minimum permissions
Troubleshooting
“Access Denied” when pushing (ECR):
- Verify controller role can assume push role
- Check push role permissions
- Ensure repository exists with correct prefix
- Verify STS session policy scope
“Connection refused” to registry (Docker):
docker-compose ps registry
docker-compose logs registry
docker-compose restart registry
Images not persisting (Docker):
docker volume ls | grep registry
docker-compose down -v # Removes volumes!
Extending Registry Support
To add a new registry provider:
- Implement
RegistryProvidertrait inrise-backend/src/registry/providers/ - Add provider to
RegistryConfigenum - Register provider in
create_registry_provider()
Potential future providers: JFrog Artifactory, GCR, ACR, GHCR, Quay.io