Three key files were created/modified to enable Render deployment:
Location: /api/Dockerfile
Change: Added dummy AWS credentials before poetry install to prevent import-time errors:
ENV AWS_ACCESS_KEY_ID=dummy_build_key \
AWS_SECRET_ACCESS_KEY=dummy_build_secret \
AWS_DEFAULT_REGION=us-east-1Why: Prowler's AWS provider imports boto3 modules that look for credentials. These dummy values prevent build failures.
Location: /render.yaml (repo root)
What: Blueprint defining 5 services:
prowler-api- Web service (Django REST API)prowler-worker- Background worker (Celery)prowler-beat- Scheduler (Celery Beat)prowler-db- PostgreSQL databaseprowler-redis- Redis cache/queue
Why: Automates deployment - just push to GitHub and connect to Render.
Location: /RENDER_DEPLOYMENT.md (repo root)
What: Complete deployment guide including:
- Step-by-step deployment instructions
- All environment variables with generation commands
- Complete API usage guide with cURL examples
- Multi-tenant workflow explanation
- Troubleshooting section
git add api/Dockerfile render.yaml RENDER_DEPLOYMENT.md
git commit -m "feat: add Render deployment configuration"
git push origin main- Go to https://dashboard.render.com
- Click New → Blueprint
- Connect your GitHub repo
- Select the
prowlerrepository - Click Apply
Render will:
- Build the Docker image
- Create PostgreSQL database
- Create Redis instance
- Deploy 3 services (API, worker, beat)
Once deployed, open Shell for prowler-api:
poetry run python manage.py migrate --database admin
poetry run python manage.py createsuperuser --database adminVisit: https://prowler-api-XXXX.onrender.com/api/v1/docs
Your App → Prowler API → Celery Queue → Worker → Prowler CLI → AWS
↓
PostgreSQL (Results)
↓
Your App ← GET /api/v1/findings
// 1. Create organization
const createOrg = async (orgName) => {
const response = await fetch('https://prowler-api-XXXX.onrender.com/api/v1/tenants', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/vnd.api+json'
},
body: JSON.stringify({
data: {
type: 'tenants',
attributes: { name: orgName }
}
})
});
return await response.json();
};
// 2. Add AWS credentials for org
const addAWSProvider = async (tenantId, awsAccessKey, awsSecretKey) => {
const response = await fetch('https://prowler-api-XXXX.onrender.com/api/v1/providers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'X-Tenant-ID': tenantId,
'Content-Type': 'application/vnd.api+json'
},
body: JSON.stringify({
data: {
type: 'providers',
attributes: {
provider: 'aws',
secret: {
aws_access_key_id: awsAccessKey,
aws_secret_access_key: awsSecretKey
}
}
}
})
});
return await response.json();
};
// 3. Trigger scan
const triggerScan = async (tenantId, providerId) => {
const response = await fetch('https://prowler-api-XXXX.onrender.com/api/v1/scans', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'X-Tenant-ID': tenantId,
'Content-Type': 'application/vnd.api+json'
},
body: JSON.stringify({
data: {
type: 'scans',
attributes: {
provider: providerId
}
}
})
});
return await response.json();
};
// 4. Poll for results
const checkScanStatus = async (tenantId, taskId) => {
const response = await fetch(
`https://prowler-api-XXXX.onrender.com/api/v1/tasks/${taskId}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'X-Tenant-ID': tenantId
}
}
);
return await response.json();
};
// 5. Get findings
const getFindings = async (tenantId, scanId) => {
const response = await fetch(
`https://prowler-api-XXXX.onrender.com/api/v1/findings?filter[scan]=${scanId}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'X-Tenant-ID': tenantId
}
}
);
return await response.json();
};✅ Multi-tenant - Each organization has isolated data and credentials ✅ Async Scans - Scans run in background via Celery ✅ Encrypted Credentials - AWS keys encrypted at rest using Fernet ✅ Auto-scaling - Add more workers as needed ✅ REST API - Standard JSON:API format ✅ Production Ready - Includes migrations, health checks, monitoring
- Credentials Storage: AWS credentials are encrypted in PostgreSQL, never in code or environment variables
- Tenant Isolation: Each API call requires
X-Tenant-IDheader for data isolation - JWT Auth: All requests need Bearer token (except login/register)
- Async Processing: Scans can take 5-30 minutes depending on AWS account size
- Worker Required: The
prowler-workerservice MUST be running for scans to process
- Deploy using the instructions above
- Test with cURL examples in
RENDER_DEPLOYMENT.md - Integrate with your external app using the example code above
- Monitor logs in Render dashboard
- Scale workers as needed for performance
For detailed information, see RENDER_DEPLOYMENT.md.