Deploy Smallstep step-ca Certificate Authority in Docker using Ansible.
This Ansible playbook automates the deployment of a private Certificate Authority (CA) using Smallstep's step-ca in a Docker container. It provides:
- Automated CA initialization with configurable DNS names and passwords
- ACME protocol support for automatic certificate issuance (like Let's Encrypt, but private)
- Configurable certificate validity durations
- Secure defaults with proper file permissions and dedicated system user
- Docker Compose based deployment for easy management
- Ansible 2.14+
- Python 3.8+
- Debian 11/12 or Ubuntu 20.04/22.04/24.04
- Docker Engine with Docker Compose v2
- Python 3.8+
- Root or sudo access
Install required collections:
ansible-galaxy install -r requirements.yml-
Clone the repository
git clone https://github.com/yourusername/ansible-stepca.git cd ansible-stepca -
Install dependencies
ansible-galaxy install -r requirements.yml
-
Configure inventory
cp inventories/example/inventory.yml inventories/production/inventory.yml # Edit with your server details -
Configure variables
cp group_vars/all/vars.example.yml group_vars/all/vars.yml # Edit with your settings -
Create vault for secrets
ansible-vault create group_vars/all/vault.yml # Add: vault_stepca_password: "your-secure-password-here"
ansible-playbook playbooks/site.ymlansible-playbook playbooks/site.yml -i inventories/production/inventory.ymlansible-playbook playbooks/site.yml --ask-vault-passansible-playbook playbooks/site.yml --check --diff| Variable | Description | Example |
|---|---|---|
stepca_init_password |
CA initialization password (min 16 chars) | {{ vault_stepca_password }} |
stepca_init_dns |
DNS names for CA certificate | ["ca.example.com", "localhost"] |
| Variable | Default | Description |
|---|---|---|
stepca_init_name |
My Private CA |
CA display name |
stepca_data_dir |
/data/step-ca/volume |
Data directory path |
stepca_compose_dir |
/data/step-ca |
Docker Compose directory |
stepca_bind_port |
8443 |
Host port to expose |
stepca_host_user |
stepca |
System user for container |
stepca_uid |
1501 |
User ID |
stepca_gid |
1501 |
Group ID |
stepca_docker_image |
smallstep/step-ca:latest |
Docker image |
stepca_container_name |
ca |
Container name |
stepca_restart_policy |
unless-stopped |
Docker restart policy |
stepca_dns_servers |
["1.1.1.1", "8.8.8.8"] |
DNS servers for container |
| Variable | Default | Description |
|---|---|---|
stepca_cert_min_duration |
5m |
Minimum certificate validity |
stepca_cert_default_duration |
2160h (90 days) |
Default certificate validity |
stepca_cert_max_duration |
8760h (1 year) |
Maximum certificate validity |
# group_vars/all/vars.yml
stepca_init_password: "{{ vault_stepca_password }}"
stepca_init_dns:
- "ca.mycompany.com"# group_vars/all/vars.yml
stepca_init_name: "MyCompany Internal CA"
stepca_init_dns:
- "ca.mycompany.com"
- "ca.internal.lan"
- "192.168.1.100"
stepca_data_dir: "/opt/step-ca/data"
stepca_compose_dir: "/opt/step-ca"
stepca_bind_port: 443
stepca_host_user: "ca-service"
stepca_uid: 2000
stepca_gid: 2000
stepca_cert_default_duration: "720h" # 30 days
stepca_cert_max_duration: "2160h" # 90 days
stepca_dns_servers:
- "10.0.0.1"
- "1.1.1.1"To use step-ca as an ACME provider for Traefik:
# traefik configuration
certificatesResolvers:
stepca:
acme:
email: [email protected]
storage: /letsencrypt/acme.json
caServer: https://ca.example.com:8443/acme/acme/directory
tlsChallenge: {}After deployment, bootstrap clients to trust your CA:
# Get the root CA fingerprint
step certificate fingerprint /data/step-ca/volume/certs/root_ca.crt
# On client machines
step ca bootstrap \
--ca-url https://ca.example.com:8443 \
--fingerprint <fingerprint-from-above>step ca certificate myserver.example.com server.crt server.keydocker logs ca
docker exec ca step ca health.
├── README.md
├── LICENSE
├── ansible.cfg
├── requirements.yml
├── playbooks/
│ └── site.yml # Main playbook
├── roles/
│ └── step_ca_docker/
│ ├── defaults/main.yml # Default variables
│ ├── tasks/main.yml # Role tasks
│ ├── templates/ # Jinja2 templates
│ ├── handlers/main.yml # Handlers
│ └── meta/main.yml # Role metadata
├── inventories/
│ └── example/
│ └── inventory.yml # Example inventory
└── group_vars/
└── all/
├── vars.example.yml # Example variables
└── vault.example.yml # Example vault
-
Check container logs:
docker logs ca
-
Verify data directory permissions:
ls -la /data/step-ca/volume
-
Ensure the password meets minimum length (16 chars)
-
Verify CA is healthy:
docker exec ca step ca health -
Check ACME provisioner is enabled:
docker exec ca step ca provisioner list
Change stepca_bind_port to an available port, or stop the conflicting service.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run linting:
ansible-lint playbooks/ roles/ - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone your fork
git clone https://github.com/yourusername/ansible-stepca.git
cd ansible-stepca
# Install dev dependencies
pip install ansible-lint yamllint
# Run linting
ansible-lint playbooks/ roles/
yamllint .- Vault passwords: Never commit unencrypted secrets
- CA private key: The CA private key at
stepca_data_dir/secrets/is critical - protect and backup - Network access: Consider firewall rules to restrict access to port 8443
- Root CA: Distribute the root certificate securely to clients
This project is licensed under the MIT License - see the LICENSE file for details.
- Smallstep for step-ca
- Ansible Community