A secure private Docker registry with basic authentication and CORS support. Easy to deploy locally or to any cloud platform.
This project provides a Docker registry setup using the official Docker Registry image with htpasswd-based authentication. It's configured with CORS headers to support browser-based UIs and can be easily deployed to any container platform.
Perfect for:
- Private Docker image hosting
- CI/CD pipelines
- Development teams needing a local registry
- Cloud deployments (AWS, GCP, Azure, and others)
- ✅ Latest Docker Registry version (
registry:2) - ✅ Basic authentication via htpasswd
- ✅ CORS support for web UIs
- ✅ Easy to deploy and configure
- ✅ Production-ready with security best practices
- Docker installed and running
htpasswdutility (or use Docker to generate passwords)
- Quick Start
- Configuration
- Cloud Deployment
- Security Best Practices
- Troubleshooting
- Contributing
- Changelog
- License
Option A: Using Docker (Recommended)
# Create htpasswd file from example
cp htpasswd.example htpasswd
# Generate a new user entry with bcrypt
docker run --rm --entrypoint htpasswd httpd:2 -Bbn username your_secure_password >> htpasswd
# Or with cost factor 12 explicitly (more secure)
docker run --rm --entrypoint htpasswd httpd:2 -Bbn -C 12 username your_secure_password >> htpasswdOption B: Using system htpasswd
# Install apache2-utils if needed
# macOS: brew install httpd
# Ubuntu/Debian: sudo apt-get install apache2-utils
# Copy example and generate entry
cp htpasswd.example htpasswd
htpasswd -Bbn username your_secure_password >> htpasswdOption A: Using htpasswd file (Local Development)
docker build -t my-registry .Option B: Using build arguments (Recommended for CI/CD/Cloud deployments)
docker build \
--build-arg USERNAME=admin \
--build-arg PASSWORD=your_secure_password \
-t my-registry .Important: The
.dockerignorefile excludeshtpasswdby default to support build arguments.When using build arguments (recommended for cloud): No action needed -
htpasswdis already excluded.When using htpasswd file: Comment out the
htpasswdline in.dockerignore:# In .dockerignore, comment out this line: # htpasswdNote: You must provide either:
- Build arguments (
USERNAMEandPASSWORD), OR- An
htpasswdfile in the build context (comment outhtpasswdin.dockerignore)If neither is provided, the build will fail with a clear error message.
docker run -d \
--name registry \
--restart=always \
-p 5050:5000 \
-v registry-data:/var/lib/registry \
-e REGISTRY_HTTP_SECRET=$(openssl rand -hex 32) \
my-registryNote:
- The htpasswd file is baked into the image during build if build args are provided
- If credentials are provided via runtime environment variables (
REGISTRY_USERNAME/REGISTRY_PASSWORD), they are generated at container startup - To update credentials, either rebuild the image with new build args or restart with new environment variables
# Login to the registry
docker login localhost:5050
# Enter your username and password
# Tag and push an image
docker tag myimage:latest localhost:5050/myimage:latest
docker push localhost:5050/myimage:latest
# Pull the image
docker pull localhost:5050/myimage:latestThis registry uses a two-component architecture:
- Docker Registry - Runs internally on
127.0.0.1:5001with authentication enabled - Nginx Proxy - Runs on port
5000and proxies requests to the registry, adding CORS headers
The entrypoint script (docker-entrypoint.sh) handles:
- Credential validation and htpasswd generation (if not provided at build time)
- Starting the registry in the background
- Waiting for the registry to be ready
- Starting Nginx as the foreground process
This architecture allows the registry to work seamlessly with browser-based UIs while maintaining security.
The registry is pre-configured with permissive CORS headers handled by an Nginx proxy. This allows it to work with browser-based UIs out of the box.
By default, Nginx will:
- Reflect the
Originheader if present (allowing credentials) - Default to
*if no Origin is present - Force these headers on all responses (including errors)
- Handle CORS preflight (OPTIONS) requests
- Support large blob uploads with extended timeouts (900s)
Since CORS is handled by Nginx, the Registry's internal CORS environment variables (REGISTRY_HTTP_HEADERS_...) will be ignored.
To customize the CORS configuration (e.g., to restrict to specific domains), you can mount a custom nginx.conf file:
docker run -d \
--name registry \
-p 5050:5000 \
-v $(pwd)/my-nginx.conf:/etc/nginx/nginx.conf:ro \
my-registryYou can use the default nginx.conf as a starting point.
The registry includes a health check endpoint at /health that returns 200 OK. This is used by Docker's healthcheck mechanism and can be accessed directly:
curl http://localhost:5050/healthThe health check verifies that both Nginx and the registry are responsive.
For production deployments, especially with load balancers, set a fixed HTTP secret:
# Generate a secret
openssl rand -hex 32
# Use it when running
docker run -d \
--name registry \
-p 5050:5000 \
-e REGISTRY_HTTP_SECRET=your_generated_secret_here \
my-registryThe registry supports credentials via build arguments (ARG) or environment variables (ENV):
Configure these build arguments in your platform's build settings:
USERNAME- Registry username (default:admin)PASSWORD- Registry password
Credentials are generated at build time and baked into the image.
Set these environment variables when running the container:
REGISTRY_USERNAME- Registry username (default:admin)REGISTRY_PASSWORD- Registry password
Credentials are generated at container startup if not provided at build time.
Set these runtime environment variables in your deployment platform:
-
REGISTRY_HTTP_SECRET- Generate with:openssl rand -hex 32- Required for load-balanced setups
- Set the same value for all instances in a multi-instance deployment
- Used for session token signing
-
REGISTRY_STORAGE_DELETE_ENABLED- Set totrueto enable deletion of images and manifests- Default:
true(enabled by default in this image) - Allows DELETE requests to remove images, tags, and manifests from the registry
- Default:
-
REGISTRY_AUTH_HTPASSWD_REALM- Authentication realm name- Default:
"Registry Realm" - This is the realm name shown in authentication challenges
- Can be customized for branding or security purposes
- Default:
-
REGISTRY_HTTP_TLS_CERTIFICATE- Path to TLS certificate file- Use with
REGISTRY_HTTP_TLS_PRIVATE_KEYto enable TLS - Example:
/auth/domain.crt
- Use with
-
REGISTRY_HTTP_TLS_PRIVATE_KEY- Path to TLS private key file- Use with
REGISTRY_HTTP_TLS_CERTIFICATEto enable TLS - Example:
/auth/domain.key
- Use with
Using Build Arguments:
docker build \
--build-arg USERNAME=admin \
--build-arg PASSWORD=your_secure_password \
-t my-registry .Using Environment Variables:
docker run -d \
--name registry \
-p 5050:5000 \
-e REGISTRY_USERNAME=admin \
-e REGISTRY_PASSWORD=your_secure_password \
-e REGISTRY_HTTP_SECRET=$(openssl rand -hex 32) \
my-registryNote: Docker may show security warnings about using
ARG PASSWORD. This is expected - the password is only used during build to generate a hash and is not stored in the final image. Only the hashed password is included in the image.
- Generate passwords with at least 16 characters
- Use a password manager
- Consider using multiple users for different purposes
- Regularly rotate passwords
For production use, you should enable TLS encryption:
# Generate self-signed certificates (for testing)
mkdir -p auth
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout auth/domain.key -x509 -days 365 \
-out auth/domain.crt \
-subj "/CN=registry.example.com"
# Run with TLS
docker run -d \
--name registry \
-p 5050:5000 \
-v "$(pwd)"/auth:/auth:ro \
-v registry-data:/var/lib/registry \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \
-e REGISTRY_HTTP_TLS_PRIVATE_KEY=/auth/domain.key \
my-registry- Restrict access using firewall rules
- Use VPN or private network
- Consider using reverse proxy (nginx/traefik) with additional security layers
- Limit access to specific IP ranges
- Keep the registry image updated
- Monitor for security advisories
- Regularly rotate passwords and secrets
- Verify the htpasswd file exists and is readable
- Check file permissions (should be readable)
- Ensure the volume mount is correct
- Verify username and password are correct
- Verify the registry is running:
docker ps - Check if port 5050 is available (or the port you mapped with
-p) - Review logs:
docker logs registry - Check if the registry started successfully: Look for "Registry is ready" in logs
- Verify Nginx started: Look for "Starting Nginx proxy" in logs
- If using runtime environment variables, ensure
REGISTRY_PASSWORDis set - If using build args, ensure
PASSWORDwas provided during build - Check entrypoint logs for credential generation messages
- Verify htpasswd file exists:
docker exec registry ls -la /auth/htpasswd
- Verify CORS headers are set correctly
- Check that the allowed origin matches your UI domain
- Ensure the registry is accessible from the browser's origin
registry/
├── .github/ # GitHub templates and workflows
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── Dockerfile # Multi-stage build: generates htpasswd and sets up registry
├── docker-entrypoint.sh # Entrypoint script: handles credential generation and starts services
├── nginx.conf # Nginx CORS proxy configuration (proxies port 5000 -> 5001)
├── .dockerignore # Prevents sensitive files in image
├── .gitignore # Git ignore patterns
├── LICENSE # MIT License
├── README.md # This file
├── CHANGELOG.md # Project version history and changes
├── CONTRIBUTING.md # Contribution guidelines
├── CODE_OF_CONDUCT.md # Code of conduct for contributors
├── SECURITY.md # Security policy and vulnerability reporting
└── htpasswd.example # Example htpasswd template
- Dockerfile: Multi-stage build that generates htpasswd credentials and sets up the registry with Nginx
- docker-entrypoint.sh: Handles runtime credential generation (if not provided at build), starts the registry on port 5001, and then starts Nginx on port 5000
- nginx.conf: Configures Nginx as a reverse proxy with CORS support, handling preflight requests and large blob uploads
Contributions are welcome! Please feel free to submit a Pull Request.
For detailed guidelines, see CONTRIBUTING.md.
Quick start:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
⚠️ Never commithtpasswdor sensitive files to version control⚠️ Use TLS in production environments⚠️ Keep registry image updated for security patches⚠️ Set a fixedREGISTRY_HTTP_SECRETfor load-balanced setups