Below is an example guide that uses a comprehensive Docker Compose file to deploy step-ca integrated with Traefik (along with several additional services). This guide follows a similar style to the previous example.
title: Deploying step-ca with Traefik Using Docker Compose
description: An example environment including step-ca, Traefik, and additional services using Docker Compose.
date: "2025-03-19"
lang: en
tags:
- step-ca
- docker-compose
- traefik
- acme
- guide
category: guide
Introduction
In this guide, we'll deploy step-ca using Docker Compose, automatically initializing the CA with the provided environment variables. In addition, Traefik is configured as a reverse proxy to handle routing, TLS certificate issuance via ACME (using our CA), and much more. You'll also see several supporting services (like whoami, portainer, authelia, and others) to create a full development environment.
Docker Compose File Overview
The following Docker Compose file defines multiple services:
- step-ca: The certificate authority service using the smallstep/step-ca image. It auto-initializes using environment variables such as
DOCKER_STEPCA_INIT_NAME
,DOCKER_STEPCA_INIT_DNS_NAMES
, and more. - Traefik: Acts as the reverse proxy and load balancer. It’s set up to expose HTTP/HTTPS endpoints, redirect traffic, and use ACME with our custom CA.
- Watchtower: Automatically updates your Docker containers.
- Whoami, Portainer, Authelia, and Others: These services demonstrate how to integrate various applications with Traefik using labels and networks.
Below is the complete Docker Compose file:
version: "3.9"
services:
step-ca:
image: smallstep/step-ca
restart: always
environment:
DOCKER_STEPCA_INIT_NAME: Smallstep
DOCKER_STEPCA_INIT_DNS_NAMES: ca.dev.local,dev.local,local
DOCKER_STEPCA_INIT_PROVISIONER_NAME: admin
DOCKER_STEPCA_INIT_PASSWORD: pass123
networks:
static-network:
aliases:
- "ca.dev.local"
traf-external:
aliases:
- "ca.dev.local"
dns:
- 11.100.100.10
extra_hosts:
- "*.dev.local:11.100.100.10"
expose:
- 9000
ports:
- 9000:9000
volumes:
- ./data/step-ca:/home/step
labels:
- "traefik.enable=true"
- 'traefik.docker.network=traf-external'
- "traefik.http.routers.ca-web.entrypoints=web"
- "traefik.http.routers.ca-web.rule=Host(`ca.dev.local`)"
- "traefik.http.middlewares.ca-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.ca-web.middlewares=ca-redirect"
- "traefik.http.routers.ca-websecure.entrypoints=websecure"
- "traefik.http.routers.ca-websecure.rule=Host(`ca.dev.local`)"
- "traefik.http.services.ca.loadbalancer.server.port=9000"
- "traefik.http.routers.ca-websecure.tls=true"
- "traefik.http.routers.ca-websecure.tls.certresolver=myresolver"
watchtower:
image: containrrr/watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
traefik:
image: traefik:v2.4
restart: always
depends_on:
- step-ca
networks:
static-network:
ipv4_address: 11.100.100.10
traf-external:
aliases:
- "traefik"
- "traefik.dev.local"
- "whoami.dev.local"
- "portainer.dev.local"
- "files.dev.local"
- "sonnar.dev.local"
- "auth.dev.local"
- "test.dev.local"
- "postiz.dev.local"
- "postiz.dev"
- "briefkasten.dev.local"
- "bedb.dev.local"
- "links.dev.local"
- "mse.dev.local"
- "tunepledge.dev.local"
- "marsuves.wiki.dev.local"
- "learndash.dev.local"
- "learndashdb.dev.local"
- "ggsa.dev.local"
- "ggsadb.dev.local"
- "opendb.dev.local"
- "posteio.dev.local"
- "mail.dev.local"
- "ghost.dev.local"
- "api.dev.local"
- "ironui.dev.local"
- "sc.dev.local"
- "msedb.dev.local"
- "stock.dev.local"
- "dr.dev.local"
- "drdb.dev.local"
- "msv.dev.local"
- "msfm.dev.local"
- "commento.dev.local"
- "msfmdb.dev.local"
- "msfdb.dev.local"
# - "ca.dev.local"
- "db.dev.local"
- "wiki.dev.local"
- "te.dev.local"
- "tedb.dev.local"
- "heimdall.dev.local"
- "dashy.dev.local"
- "organizr.dev.local"
- "bookstack.dev.local"
- "excalidraw.dev.local"
- "mailhog.dev.local"
- "admin-ldap.dev.local"
- "myvault.dev.local"
- "keycloak.dev.local"
- "jellyfinn.dev.local"
- "vault.dev.local"
- "ninja.dev.local"
command:
- "--api.insecure=true"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traf-external"
- "--providers.file.watch=true"
- "--accesslog=false"
- "--log=true"
- "--log.level=DEBUG"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesResolvers.myresolver.acme.tlsChallenge=true"
- "--certificatesResolvers.myresolver.acme.email=admin"
- "--certificatesResolvers.myresolver.acme.storage=/etc/acme/acme.json"
- "--certificatesresolvers.myresolver.acme.caserver=https://ca.dev.local:9000/acme/acme/directory"
- "--certificatesResolvers.myresolver.acme.httpChallenge=true"
- "--certificatesResolvers.myresolver.acme.httpChallenge.entryPoint=websecure"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data/step-ca/certs/root_ca.crt:/usr/local/share/ca-certificates/my_root_ca.crt
- ./site_home_local.crt:/certs/wild_dev_local.crt:ro
- ./site_home_local.key:/certs/wild_dev_local.key:ro
- ./ca.crt:/certs/ca.crt
- ./traefik_config/traefik.yml:/traefik.yml:ro
- ./traefik_config/log/access.log:/log/access.log
- ./traefik_config/acme/acme.json:/etc/acme/acme.json
- ./traefik_config/configurations:/configurations
environment:
LEGO_CA_CERTIFICATES: "/usr/local/share/ca-certificates/my_root_ca.crt"
LEGO_CA_SERVER_NAME: "ca.dev.local"
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik0.entrypoints=web"
- "traefik.http.routers.traefik0.rule=Host(`traefik.dev.local`)"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
- "traefik.http.middlewares.traefik-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.traefik0.middlewares=traefik-redirect"
- "traefik.http.routers.traefik1.entrypoints=websecure"
- "traefik.http.routers.traefik1.rule=Host(`traefik.dev.local`)"
- "traefik.http.routers.traefik1.tls=true"
- "traefik.http.routers.traefik1.tls.certresolver=myresolver"
whoami:
image: containous/whoami:latest
hostname: "whoami1"
networks:
- traf-external
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami0.entrypoints=web"
- "traefik.http.routers.whoami0.rule=Host(`whoami.dev.local`)"
- "traefik.http.middlewares.whoami-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.whoami0.middlewares=whoami-redirect"
- "traefik.http.routers.whoami1.entrypoints=websecure"
- "traefik.http.routers.whoami1.rule=Host(`whoami.dev.local`)"
- "traefik.http.routers.whoami1.tls=true"
- "traefik.http.routers.whoami1.tls.certresolver=myresolver"
portainer:
image: portainer/portainer-ee:latest
command: -H unix:///var/run/docker.sock
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
- ./docker/portainer/data:/data
ports:
- 9001:9000
networks:
traf-external:
aliases:
- "portainer.dev.local"
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer-web.entrypoints=web"
- "traefik.http.routers.portainer-web.rule=Host(`portainer.dev.local`)"
- "traefik.http.middlewares.portainer-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.portainer-web.middlewares=portainer-redirect"
- "traefik.http.routers.portainer-websecure.entrypoints=websecure"
- "traefik.http.routers.portainer-websecure.rule=Host(`portainer.dev.local`)"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
- "traefik.http.routers.portainer-websecure.tls=true"
- "traefik.http.routers.portainer-websecure.tls.certresolver=myresolver"
####DB for Authelia
mariadb:
container_name: authelia_mariadb
image: mariadb:$MARIAVERSION
hostname: authelia_mariadb
restart: always
networks:
- traf-external
volumes:
- ./docker/mariadb/config:/config
- ./docker/mariadb/mysql:/var/lib/mysql
expose:
- 3306
environment:
- MYSQL_ROOT_PASSWORD=$AUTHELIA_MYSQL_ROOT_PASSWORD
- MYSQL_USER=$AUTHELIA_MYSQL_USER
- MYSQL_DATABASE=$AUTHELIA_MYSQL_DATABASE
- MYSQL_PASSWORD=$AUTHELIA_MYSQL_PASSWORD
labels:
- traefik.enable=false
####GUI management for mariadb
phpmyadmin:
container_name: authelia_phpmyadmin
image: phpmyadmin/phpmyadmin:$PHPMYADMINVERSION
depends_on:
- mariadb
networks:
- traf-external
environment:
PMA_HOSTS: authelia_mariadb
UPLOAD_LIMIT: 1G
PMA_ARBITRARY: 1
PMA_DB_ENGINE: mysql
PMA_HOST: mariadb
PMA_PORT: 3306
PMA_USER: $AUTHELIA_MYSQL_USER
PMA_PASSWORD: $AUTHELIA_MYSQL_PASSWORD
MYSQL_ROOT_PASSWORD: $AUTHELIA_MYSQL_ROOT_PASSWORD
labels:
- traefik.enable=true
- traefik.http.routers.phpmyadmin-http-rtr.rule=Host(`db.$DOMAINNAME`)
- traefik.http.routers.phpmyadmin-http-rtr.entrypoints=web
- traefik.http.routers.phpmyadmin-https-rtr.rule=Host(`db.$DOMAINNAME`)
- traefik.http.routers.phpmyadmin-https-rtr.entrypoints=websecure
- traefik.http.routers.phpmyadmin-https-rtr.tls=true
- traefik.http.routers.phpmyadmin-https-rtr.tls.certresolver=myresolver
- traefik.http.routers.phpmyadmin-http-rtr.middlewares=middlewares-https-redirect@file
- traefik.http.routers.phpmyadmin-https-rtr.middlewares=chain-authelia@file
####Redis cache for Authelia
redis:
container_name: authelia_redis
image: redis:alpine
restart: always
volumes:
- ./docker/redis:/data
networks:
- traf-external
expose:
- 6379
environment:
- TZ=$TZ
labels:
- traefik.enable=false
####Authelia (Lite) - Self-Hosted Single Sign-On and Two-Factor Authentication
authelia:
container_name: authelia
image: authelia/authelia:$AUTHELIAVERSION
restart: always
depends_on:
- mariadb
- phpmyadmin
- redis
ports:
- 9091:9091
expose:
- 9091
environment:
- TZ=$TZ
- AUTHELIA_THEME=dark
volumes:
- ./docker/authelia/authelia:/var/lib/authelia
- ./docker/authelia/configuration.yml:/etc/authelia/configuration.yml:ro
- ./docker/authelia/users_database.yml:/etc/authelia/users_database.yml
networks:
- traf-external
labels:
- traefik.enable=true
- traefik.http.routers.authelia.rule=Host(`auth.$DOMAINNAME`)
- traefik.http.routers.authelia.entrypoints=web,websecure
- traefik.http.routers.authelia.tls=true
- traefik.http.routers.authelia.tls.certresolver=myresolver
####Dozzle - A Web-Based Docker Log Viewer
dozzle:
container_name: dozzle_logs
image: amir20/dozzle:$DOZZLEVERSION
restart: always
networks:
- traf-external
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 9999:8080
labels:
- traefik.enable=false
####Mailhog For Local Testing
mailhog:
container_name: mailhog
restart: always
image: mailhog/mailhog:latest
expose:
- 8025
- 1025
networks:
traf-external:
aliases:
- "mailhog.dev.local"
mailhog:
aliases:
- "mailhog"
- "mailhog.dev.local"
labels:
- traefik.enable=true
- traefik.http.routers.mailhog.rule=Host(`mailhog.$DOMAINNAME`)
- traefik.http.routers.mailhog.entrypoints=web
- traefik.http.routers.mailhog-secure.rule=Host(`mailhog.$DOMAINNAME`)
- traefik.http.routers.mailhog-secure.entrypoints=websecure
- traefik.http.routers.mailhog-secure.tls=true
- traefik.http.routers.mailhog-secure.tls.certresolver=myresolver
- traefik.http.services.mailhog-secure.loadbalancer.server.port=8025
- traefik.http.routers.mailhog.middlewares=middlewares-https-redirect@file
- traefik.http.routers.mailhog-secure.middlewares=chain-authelia@file
####Whoami Test for the Front Page
whoami2:
container_name: frontpage
image: containous/whoami
restart: always
depends_on:
- traefik
expose:
- 80
networks:
- traf-external
labels:
- traefik.enable=true
- traefik.http.routers.whoami-http-rtr.rule=Host(`test.$DOMAINNAME`)
- traefik.http.routers.whoami-http-rtr.entrypoints=web
- traefik.http.routers.whoami-https-rtr.rule=Host(`test.$DOMAINNAME`)
- traefik.http.routers.whoami-https-rtr.entrypoints=websecure
- traefik.http.routers.whoami-https-rtr.tls=true
- traefik.http.routers.whoami-https-rtr.tls.certresolver=myresolver
- traefik.http.routers.whoami-http-rtr.middlewares=middlewares-https-redirect@file
- traefik.http.routers.whoami-https-rtr.middlewares=chain-authelia@file
####Heimdall App Dashboard
heimdall:
image: lscr.io/linuxserver/heimdall:latest
restart: always
container_name: heimdall
environment:
- PUID=1000
- PGID=1000
- TZ=$TZ
depends_on:
- traefik
networks:
traf-external:
aliases:
- "heimdall.dev.local"
labels:
- traefik.enable=true
- traefik.http.routers.heimdall-http.rule=Host(`heimdall.$DOMAINNAME`)
- traefik.http.routers.heimdall-http.entrypoints=web
- traefik.http.routers.heimdall.rule=Host(`heimdall.$DOMAINNAME`)
- traefik.http.routers.heimdall.entrypoints=websecure
- traefik.http.routers.heimdall.tls=true
- traefik.http.routers.heimdall.tls.certresolver=myresolver
- "traefik.http.services.heimdall.loadbalancer.server.port=80"
- traefik.http.routers.heimdall-http.middlewares=middlewares-https-redirect@file
- traefik.http.routers.heimdall.middlewares=chain-authelia@file
volumes:
- ./data/heimdall:/config
openldap:
image: osixia/openldap:latest
container_name: openldap
volumes:
- ./data/ldap/db:/var/lib/ldap
- ./data/ldap/conf:/etc/ldap/slapd.d
networks:
- traf-external
expose:
- 389
- 636
restart: always
environment:
TZ: $TZ
LDAP_ORGANISATION: "dev"
LDAP_DOMAIN: "dev.local"
LDAP_BASE_DN: "dc=dev,dc=local"
ldap-php:
image: osixia/phpldapadmin
networks:
- traf-external
ports:
- 8786:80
depends_on:
- openldap
environment:
PHPLDAPADMIN_LDAP_HOSTS: openldap
PHPLDAPADMIN_HTTPS: false
ldap-user-manager:
image: wheelybird/ldap-user-manager:next_release
container_name: ldap-user-manager
networks:
- traf-external
ports:
- 8785:80
restart: always
depends_on:
- openldap
environment:
TZ: $TZ
SERVER_HOSTNAME: "admin-ldap.dev.local"
ORGANISATION_NAME: "dev"
LDAP_URI: "ldap://openldap"
LDAP_BASE_DN: "dc=dev,dc=local"
LDAP_REQUIRE_STARTTLS: "FALSE"
LDAP_ADMINS_GROUP: "admins"
LDAP_ADMIN_BIND_DN: "cn=admin,dc=dev,dc=local"
LDAP_ADMIN_BIND_PWD: "admin"
LDAP_DEBUG: "true"
EMAIL_DOMAIN: "dev.local"
NO_HTTPS: "true"
SMTP_HOSTNAME: "mailhog"
SMTP_HOST_PORT: 1025
SMTP_USERNAME: ""
SMTP_PASSWORD: ""
SMTP_USE_TLS: "false"
EMAIL_FROM_ADDRESS: "[email protected]"
REMOTE_HTTP_HEADERS_LOGIN: "TRUE"
labels:
- traefik.enable=true
- traefik.http.routers.ldapuser.rule=Host(`admin-ldap.$DOMAINNAME`)
- traefik.http.routers.ldapuser.entrypoints=web
- traefik.http.routers.ldapuser-secure.rule=Host(`admin-ldap.$DOMAINNAME`)
- traefik.http.routers.ldapuser-secure.entrypoints=websecure
- traefik.http.routers.ldapuser-secure.tls=true
- traefik.http.routers.ldapuser-secure.tls.certresolver=myresolver
- traefik.http.services.ldapuser-secure.loadbalancer.server.port=80
- traefik.http.routers.ldapuser.middlewares=middlewares-https-redirect@file
- traefik.http.routers.ldapuser-secure.middlewares=chain-authelia@file
volumes:
portainer_data:
networks:
mailhog:
external: true
name: mailhog
traf-external:
external: true
name: traf-external
static-network:
ipam:
config:
- subnet: 11.100.100.0/24
default:
driver: bridge
Step-by-Step Instructions
1. Customize Your Environment
- Environment Variables: Adjust values (like passwords, domain names, and version numbers) as needed.
- Volumes & Networks: Ensure that your
./data
,./traefik_config
, and other referenced directories exist. - DNS & Extra Hosts: Modify DNS settings if your local network differs.
2. Deploy the Environment
From your project directory (where the docker-compose.yml
file is located), start all services with:
docker-compose up -d
This command will pull images, initialize the CA with the provided settings, and start Traefik along with all other defined services.
3. Verify the Setup
- step-ca Health Check: Visit https://ca.dev.local:9000/health to confirm that step-ca is running.
- Traefik Dashboard: If enabled (via insecure API), check Traefik’s dashboard at http://traefik.dev.local:8080.
- ACME Integration: Traefik uses the ACME resolver with your custom CA. Validate that HTTPS routes (e.g.,
whoami.dev.local
,portainer.dev.local
) are working as expected.
Additional Services
This setup includes several useful services:
- Watchtower: Automatically monitors and updates your containers.
- Whoami: A simple service to test routing and headers.
- Portainer: A web UI for managing Docker environments.
- Authelia, phpMyAdmin, Redis, and More: These services create a robust local development environment with user authentication, database management, and monitoring.
Conclusion
This comprehensive Docker Compose example demonstrates how to deploy step-ca alongside Traefik and other services, creating a fully integrated environment for local development and testing. Customize the file as needed and enjoy secure, automated certificate management with your own CA!
Feel free to provide feedback or reach out if you have any questions. Happy deploying!