A comprehensive reference implementation demonstrating SSL/TLS certificate configuration for ASP.NET Core applications running in Docker containers on Linux, with cross-platform certificate management using PowerShell Core and .NET X509Store API.
Project Focus: Docker SSL/TLS configuration, not invoice management (the API is just a demo application)
This project demonstrates best practices for:
- Self-signed certificate generation for development environments
- X509Store certificate management on Linux and WSL
- Cross-platform certificate deployment (WSL, Linux, Docker)
- Kestrel HTTPS configuration with custom certificates
- Automated certificate installation with PowerShell Core
- Non-root Docker container certificate handling
- Red Hat Universal Base Image (UBI) deployment for enterprise compliance
The project includes a minimal REST API (invoice management) to demonstrate the certificate configuration in a real-world application context.
This project uses Red Hat Universal Base Images (UBI) instead of Microsoft's official .NET container images due to corporate requirements for enterprise support and compliance.
Key Benefits:
- Enterprise support and long-term maintenance from Red Hat
- Security-focused updates through Red Hat's RHEL repositories
- Compliance with corporate container image policies
- Production-ready for regulated environments
Custom Base Images: This project builds two custom base images from Red Hat UBI8:
artemis/ubi8-dotnet-sdk:9.0- Build environment with .NET 9.0 SDK (~680MB)artemis/ubi8-aspnet-runtime:9.0- Runtime with ASP.NET Core 9.0 and PowerShell Core 7+ (~440MB)
Both images include .NET 9.0 and PowerShell Core 7+ from Microsoft's official RHEL repositories, ensuring feature parity with Microsoft images.
Image Size Tradeoff: The UBI8-minimal base (~96MB) is smaller than Microsoft's aspnet base (~220MB), but the final artemis runtime image is ~440MB due to PowerShell Core 7+ requirement for cross-platform certificate automation. This is approximately double Microsoft's .NET image size (~220MB), but necessary for automated X509Store certificate management in containers.
Get up and running in 5 simple steps:
pwsh ./build-base-images.ps1pwsh ./Setup-Certificates.ps1pwsh ./Start-DockerCompose.ps1# Check container status
docker-compose ps
# View logs
docker-compose logs -f artemis-api
# Test HTTP endpoint
curl http://localhost:5000/api/invoices
# Test HTTPS endpoint
curl -k https://localhost:5001/api/invoices
# Verify certificates installed
docker exec artemis-invoicing-api pwsh -Command \
'$store = [System.Security.Cryptography.X509Certificates.X509Store]::new("My", "CurrentUser"); \
$store.Open("ReadOnly"); \
Write-Host "Certificates: $($store.Certificates.Count)"; \
$store.Close()'docker-compose downThat's it! Your ASP.NET Core API is now running with SSL/TLS certificates in Docker. 🎉
For detailed explanations and troubleshooting, see the sections below.
Configuring SSL/TLS certificates for ASP.NET Core in Docker on Linux is non-trivial:
- Windows Certificate Store doesn't exist on Linux
- .NET uses a different certificate store structure on Linux (
~/.dotnet/corefx/cryptography/x509stores/) - Simply copying certificate files is insufficient - certificates must be registered via X509Store API
- Docker containers require special handling for certificate mounting and installation
- Cross-platform certificate management requires PowerShell Core (pwsh)
- Certificate chain validation works differently on Linux vs Windows
This project solves these challenges with a complete, working implementation that you can use as a reference for your own projects.
This project demonstrates:
- Creating self-signed CA certificates with OpenSSL
- Generating server certificates signed by custom CA
- Exporting certificates in multiple formats (PFX, CRT)
- Proper Subject Alternative Names (SAN) configuration
- Understanding .NET certificate store structure on Linux
- Using X509Store API for certificate installation
- Certificate chain validation on Linux
- Proper file permissions and ownership
- PowerShell Core (pwsh) for Linux/Docker
- Automated certificate deployment scripts
- Certificate verification and testing utilities
- Platform-agnostic certificate management
- Certificate volume mounts in Docker
- Runtime certificate installation patterns
- Non-root container security considerations
- Docker Compose for development workflows
- Kestrel certificate configuration
- Certificate thumbprint-based loading
- Fallback to development certificates
- appsettings.json certificate configuration
- ✅ Self-signed CA and server certificate generation
- ✅ Automated installation to .NET X509Store (Root and My stores)
- ✅ Cross-platform PowerShell scripts (pwsh)
- ✅ Docker container certificate mounting
- ✅ Certificate chain validation
- ✅ Automated thumbprint configuration
- ✅ Multi-stage Dockerfile optimized for Linux
- ✅ Certificate installation during container startup
- ✅ Non-root container execution (app user)
- ✅ Docker Compose configuration
- ✅ Health checks and monitoring
- ✅ Simple REST API (invoice management)
- ✅ Kestrel with custom certificate configuration
- ✅ Swagger/OpenAPI documentation
- ✅ Proper HTTPS endpoint configuration
- Docker and Docker Compose
- PowerShell Core (pwsh) 7.0 or later
- .NET 9.0 SDK (for local development)
- Linux, WSL, or macOS (Windows support limited to local development)
- Access to Red Hat UBI registry (registry.access.redhat.com - publicly available)
Run the certificate setup script:
pwsh ./Setup-Certificates.ps1This creates certificates in ~/certs/ and installs them to X509Store.
Build the custom Red Hat UBI base images:
pwsh ./build-base-images.ps1Start the application:
pwsh ./Start-DockerCompose.ps1The application will be available at:
- HTTPS:
https://localhost:5001 - HTTP:
http://localhost:5000 - Swagger UI:
https://localhost:5001/
Test that the custom certificate is working:
# Verify HTTPS connection
curl -k https://localhost:5001/api/invoices
# Run certificate validation tests
pwsh ./Test-CertificateSetup.ps1
# List certificates in container
docker exec artemis-api pwsh -Command "Get-ChildItem Cert:\\CurrentUser\\My"All automation scripts use PowerShell Core (pwsh) for cross-platform compatibility:
Wrapper script for Docker Compose with automatic certificate management:
# Start containers (reads certificate files automatically)
pwsh ./Start-DockerCompose.ps1
# Build images
pwsh ./Start-DockerCompose.ps1 -Command build
# View logs
pwsh ./Start-DockerCompose.ps1 -Command logs
# Stop containers
pwsh ./Start-DockerCompose.ps1 -Command downContainer startup script that:
- Validates PowerShell availability
- Runs Install-DockerCertificates.ps1
- Starts the .NET application
Platform Support:
- ✅ Windows (PowerShell 7+)
- ✅ Linux (PowerShell 7+)
- ✅ macOS (PowerShell 7+)
- ✅ Docker (Red Hat UBI with PowerShell)
On Linux, .NET Core uses a file-based certificate store located at:
$HOME/.dotnet/corefx/cryptography/x509stores/
├── root/ # Trusted Root CA certificates
│ └── <hash>.pfx
├── my/ # Personal certificates (with private keys)
│ ├── <thumbprint>.pfx
│ └── <thumbprint>.crt
└── ca/ # Intermediate CA certificates
For the Docker container (running as artemis user):
- Store path:
/home/artemis/.dotnet/corefx/cryptography/x509stores/
For WSL/Linux development:
- Store path:
~/.dotnet/corefx/cryptography/x509stores/
Simply copying certificate files to these directories is insufficient. Certificates must be:
- Loaded into memory as X509Certificate2 objects with proper flags
- Added to the appropriate X509Store via the .NET API
- Properly indexed by thumbprint and hash
- Validated and trusted by the certificate chain
The PowerShell scripts in this project use the X509Store API to ensure certificates are correctly installed:
# Install certificate to CurrentUser\My store
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
[System.Security.Cryptography.X509Certificates.StoreName]::My,
[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
)
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($cert)
$store.Close()[Artemis Root CA] (CurrentUser\Root)
↓
[artemis-api.local] (CurrentUser\My)
The application loads the server certificate from CurrentUser\My, and the .NET framework validates it against the CA certificate in CurrentUser\Root.
Docker containers require special handling:
-
Certificate files mounted as volumes
- Source:
~/projs/dotnet/artemis.svc/certs/ - Target:
/certs/(inside container)
- Source:
-
Installation script run during container startup
docker-entrypoint.ps1executed as ENTRYPOINT- Calls
Install-DockerCertificates.ps1for certificate installation - Installs CA to Root store
- Installs server certificate to My store
- Starts the .NET application
-
Proper file permissions
- PFX files: 600 (read/write for owner only)
- CRT files: 644 (read for all)
- Owner:
artemisuser (non-root)
-
Non-root user access
- Container runs as
artemisuser (UID 1000) - Certificate store:
/home/artemis/.dotnet/corefx/cryptography/x509stores/ - PowerShell scripts run as
artemisuser
- Container runs as
This project uses runtime certificate installation with Red Hat UBI for secure, flexible deployments.
Key Advantages:
- Certificates not baked into image
- Easy certificate rotation without rebuild
- Same image works across environments
- Enterprise-grade UBI base with RHEL security updates
Run the setup script to create certificates:
pwsh ./Setup-Certificates.ps1This creates certificates in ~/certs/ and updates appsettings.json with the thumbprint.
pwsh ./build-base-images.ps1docker build -t artemis-api:latest .pwsh ./Start-DockerCompose.ps1# Check logs
docker logs artemis-api
# Test HTTPS
curl -k https://localhost:5001/api/invoicesThe docker-compose.yml demonstrates volume mounting for certificates:
services:
artemis-api:
build: .
ports:
- "5000:5000"
- "5001:5001"
volumes:
- ./certs:/certs:ro # Mount certificates read-only
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:5001;http://+:5000Volume mount details:
- Source:
./certs(relative to docker-compose.yml) - Target:
/certs(inside container) - Mode:
ro(read-only for security)
Multi-stage build using Red Hat UBI custom base images:
# Stage 1: Build
FROM artemis/ubi8-dotnet-sdk:9.0 AS build
WORKDIR /src
COPY ["artemis.svc.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build
# Stage 2: Publish
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
# Stage 3: Runtime
FROM artemis/ubi8-aspnet-runtime:9.0 AS final
WORKDIR /app
COPY --from=publish --chown=artemis:artemis /app/publish .
COPY --chown=artemis:artemis Install-DockerCertificates.ps1 /app/
COPY --chown=artemis:artemis docker-entrypoint.ps1 /app/
USER artemis
EXPOSE 5000 5001
ENTRYPOINT ["pwsh", "-File", "/app/docker-entrypoint.ps1"]Key Features:
- Red Hat UBI8 base images (enterprise compliance)
- .NET 9.0 from Microsoft RHEL repositories
- PowerShell Core 7+ for certificate automation
- Non-root user (artemis, UID 1000)
- Runtime certificate installation via entrypoint
To rotate certificates:
- Generate new certificates:
pwsh ./Setup-Certificates.ps1 - Restart container:
docker-compose restart
The container picks up new certificates from the mounted volume on startup.
Generates self-signed CA and server certificates, installs them to X509Store, and updates appsettings.json.
pwsh ./Setup-Certificates.ps1Installs certificates inside Docker containers at runtime (called automatically by entrypoint).
docker exec artemis-api pwsh ./Install-DockerCertificates.ps1Verifies certificate installation and chain validation.
pwsh ./Test-CertificateSetup.ps1artemis.svc/
├── Controllers/
│ └── InvoicesController.cs # REST API endpoints (demo)
├── Models/
│ └── Invoice.cs # Invoice entity (demo)
├── Services/
│ ├── IInvoiceService.cs # Service interface (demo)
│ └── InMemoryInvoiceService.cs # In-memory storage (demo)
├── certs/ # Certificate files (generated, gitignored)
│ ├── ca/
│ │ ├── artemis-ca.pfx # CA certificate with private key
│ │ ├── artemis-ca.crt # CA certificate (public)
│ │ └── artemis-ca.key # CA private key
│ └── server/
│ ├── artemis-server.pfx # Server certificate with private key
│ ├── artemis-server.crt # Server certificate (public)
│ ├── artemis-server.key # Server private key
│ └── artemis-server.csr # Certificate signing request
├── Setup-Certificates.ps1 # Certificate generation script
├── Install-DockerCertificates.ps1 # Docker certificate installer
├── docker-entrypoint.ps1 # Container entrypoint (PowerShell)
├── Start-DockerCompose.ps1 # Docker Compose wrapper (PowerShell)
├── Test-CertificateSetup.ps1 # Certificate verification script
├── build-base-images.ps1 # Red Hat UBI base image builder
├── DOCKER_CERTIFICATE_INTEGRATION.md # Implementation details
├── POWERSHELL_QUICK_REFERENCE.md # PowerShell commands reference
├── Program.cs # Application entry point & Kestrel config
├── Dockerfile # Multi-stage Docker build (UBI-based)
├── Dockerfile.ubi8-dotnet-sdk # Red Hat UBI8 with .NET 9.0 SDK
├── Dockerfile.ubi8-aspnet-runtime # Red Hat UBI8-minimal with ASP.NET Core
├── docker-compose.yml # Docker Compose configuration
├── appsettings.json # Certificate thumbprint configuration
└── artemis.svc.csproj # Project file
The project includes a minimal REST API for invoice management to demonstrate the certificate configuration in a real-world application context.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/invoices |
List all invoices |
| GET | /api/invoices/{id} |
Get invoice by ID |
| POST | /api/invoices |
Create new invoice |
| PUT | /api/invoices/{id} |
Update invoice |
| DELETE | /api/invoices/{id} |
Delete invoice |
{
"id": 1,
"invoiceNumber": "INV-2025-001",
"date": "2025-10-03T10:30:00Z",
"customerName": "Acme Corporation",
"amount": 1500.00,
"status": 1
}Invoice Status Values:
0- Draft1- Sent2- Paid3- Overdue4- Cancelled
# Verify HTTPS is working with custom certificate
curl -k https://localhost:5001/api/invoices
# Access Swagger UI
open https://localhost:5001
# Create a test invoice
curl -k -X POST https://localhost:5001/api/invoices \
-H "Content-Type: application/json" \
-d '{
"invoiceNumber": "INV-2025-999",
"date": "2025-10-04T00:00:00Z",
"customerName": "Test Customer",
"amount": 1000.00,
"status": 0
}'Note: The invoice API uses in-memory storage and is for demonstration purposes only. Data is lost when the container restarts.
- .NET 9.0: ASP.NET Core Web API
- Kestrel: High-performance web server with custom certificate support
- X509Store API: Cross-platform certificate management
- PowerShell Core (pwsh): Certificate automation scripts
- Red Hat UBI 8: Enterprise-grade base images (UBI8 and UBI8-minimal)
- Docker: Container runtime for Linux deployment
- OpenSSL: Certificate generation (via PowerShell)
- Swagger/OpenAPI: API documentation
The application loads certificates based on thumbprint configuration:
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://0.0.0.0:5001"
},
"Http": {
"Url": "http://0.0.0.0:5000"
}
}
},
"CertificateSettings": {
"Thumbprint": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
"StoreName": "My",
"StoreLocation": "CurrentUser"
}
}Configuration options:
Thumbprint: Certificate thumbprint (no spaces or colons)StoreName: Certificate store name (My,Root,CA)StoreLocation: Store location (CurrentUser,LocalMachine)
The application loads certificates using the X509Store API:
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(
X509FindType.FindByThumbprint,
thumbprint,
validOnly: false
);
if (certs.Count > 0)
{
options.ServerCertificate = certs[0];
}
}Override configuration using environment variables:
# Override certificate thumbprint
export CertificateSettings__Thumbprint="YOUR_THUMBPRINT_HERE"
# Override Kestrel endpoints
export Kestrel__Endpoints__Https__Url="https://0.0.0.0:5001"
# Set ASP.NET Core environment
export ASPNETCORE_ENVIRONMENT=ProductionIssue: Certificate chain validation fails
Solution:
# Check logs and verify installation
docker logs artemis-api
docker exec artemis-api pwsh ./Test-CertificateSetup.ps1Issue: Container exits immediately
Solution:
# Check logs and certificate thumbprint
docker logs artemis-api
docker exec artemis-api cat appsettings.jsonIssue: "pull access denied for artemis/ubi8-dotnet-sdk"
Solution:
# Build base images first
pwsh ./build-base-images.ps1
docker images | grep artemis/ubi8Issue: "Cannot find path '/certs/artemis-api.pfx'"
Solution:
# Generate certificates and verify mount
pwsh ./Setup-Certificates.ps1
ls -la ~/certs/
docker exec artemis-api ls -la /certs/Issue: "certificate verify failed"
Solution:
# Use -k flag for self-signed certificates
curl -k https://localhost:5001/api/invoices
# Or install CA certificate in client's trusted store-
Certificate Storage
- Private keys stored in PFX files with proper permissions (600)
- Certificate directory mounted read-only in Docker (
:ro) - Certificates not embedded in Docker images
-
Certificate Expiration
- Monitor certificate expiration dates
- Setup: 2-year server certificate, 5-year CA certificate
- Use
Test-CertificateSetup.ps1to check expiration
-
Private Key Protection
- PFX files contain private keys (keep secure)
- Never commit PFX files to version control (add to
.gitignore) - Use proper file permissions (600 on Linux)
-
Container Security
- Container runs as non-root user (
artemis, UID 1000) - Certificates installed to user store, not system store
- Read-only volume mounts for certificates
- Container runs as non-root user (
-
Development vs. Production
- This setup is for development only
- Production should use certificates from trusted CA
- Consider using Let's Encrypt or Azure Key Vault for production
-
Input Validation
- API includes basic input validation
- Error responses don't expose sensitive information
- HTTPS redirection enabled in production
You can also run the application locally for development:
- .NET 9.0 SDK
- PowerShell Core (pwsh)
- Linux, WSL, or macOS
-
Generate certificates:
pwsh ./Setup-Certificates.ps1
-
Build the application:
dotnet build
-
Run the application:
dotnet run
-
Access the application:
- HTTPS:
https://localhost:5001 - HTTP:
http://localhost:5000 - Swagger:
https://localhost:5001/
- HTTPS:
Certificates are installed to:
~/.dotnet/corefx/cryptography/x509stores/my/(server certificate)~/.dotnet/corefx/cryptography/x509stores/root/(CA certificate)
This reference implementation focuses on development environments. For production:
- Obtain certificates from trusted CA (Let's Encrypt, DigiCert, etc.)
- Avoid self-signed certificates in production
- Ensure proper certificate chain validation
- Use Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault
- Implement certificate rotation policies
- Monitor certificate expiration with alerting
- Use cert-manager for automated certificate management
- Store certificates in Kubernetes Secrets
- Use Ingress controllers for TLS termination
- Implement proper authentication and authorization
- Use HTTPS redirection (enabled by default)
- Enable HSTS (HTTP Strict Transport Security)
- Configure proper CORS policies
- Implement rate limiting and DDoS protection
- Implement structured logging
- Monitor certificate expiration dates
- Track SSL/TLS handshake failures
- Set up health checks and readiness probes
- ASP.NET Core HTTPS Configuration
- Kestrel Web Server Configuration
- X509Store Class Documentation
- PowerShell Core Documentation
- .NET on RHEL Documentation
- Docker Certificate Integration - Certificate implementation details
This is a reference implementation project. If you find issues or have improvements:
- Test your changes with the provided scripts
- Ensure Docker deployment works end-to-end
- Update documentation to reflect changes
- Test on Linux/WSL environment
This project is licensed under the MIT License - see the LICENSE file for details.
This is an open source educational project designed to help developers understand SSL/TLS certificate configuration for ASP.NET Core applications in Docker containers. You are free to use, modify, and distribute this code for both personal and commercial purposes.
Contributions, improvements, and feedback are welcome and encouraged.
For issues and questions:
- Check the Troubleshooting section above
- Review application logs:
docker logs artemis-api - Run certificate tests:
pwsh ./Test-CertificateSetup.ps1 - Consult the reference documentation links
Remember: This project demonstrates SSL/TLS certificate configuration for Docker deployments. The invoice API is just a minimal example application to demonstrate the certificate setup in a real-world context.