Contributing
All docsContributions are welcome. The canonical, always-current details live in the repository — this page points you to them.
Where to start
- Browse the source and open an issue to report a bug or propose a change.
- Read the Contributing section of the README for the workflow and conventions.
- Before a pull request can be merged you must sign the project's Contributor License Agreement. A bot walks you through signing when you open your first PR.
Reporting a vulnerability
Please do not open a public issue for security problems. Follow the private disclosure process in SECURITY.md.
Local development environment
The shipped compose.yaml is the development stack — nginx +
PHP-FPM + PostgreSQL — with common tasks wrapped in a
justfile:
git clone https://github.com/ubermuda/qualendar.git
cd qualendar
docker compose up -d # start containers
composer install # install PHP dependencies
just migrate-run # run database migrations
Its services attach to an external traefik network
and request certificates from a step-ca resolver — which is what
makes https://<project>.dev.localhost resolve with a trusted
certificate. That proxy is a separate stack; it is not created
by docker compose up here. (For a production deployment you don't
need any of it — see Self-hosting.)
The Traefik + step-ca proxy
Stand it up once on the shared traefik network. step-ca is a local
ACME certificate authority, so Traefik issues trusted certificates for
*.dev.localhost automatically on first request. Put this in a
separate directory (e.g. proxy/compose.yml):
services:
dnsmasq: # resolves *.dev.localhost inside the network
image: jpillora/dnsmasq:1.1.0
restart: unless-stopped
cap_add: [NET_ADMIN]
volumes:
- ./dnsmasq.conf:/etc/dnsmasq.conf:ro
networks:
traefik: { ipv4_address: 172.20.0.3 }
step-ca: # local ACME certificate authority
image: smallstep/step-ca:0.30.2
restart: unless-stopped
dns: ["172.20.0.3"]
environment:
DOCKER_STEPCA_INIT_NAME: "Local Dev CA"
DOCKER_STEPCA_INIT_DNS_NAMES: "step-ca,localhost"
DOCKER_STEPCA_INIT_ACME: "true"
DOCKER_STEPCA_INIT_PASSWORD_FILE: "/home/step/secrets/password"
PWDPATH: "/home/step/secrets/password"
volumes:
- ./step-ca:/home/step
healthcheck:
test: ["CMD", "wget", "-qO-", "--no-check-certificate", "https://localhost:9000/health"]
interval: 5s
retries: 20
start_period: 15s
networks:
traefik: { ipv4_address: 172.20.0.4 }
traefik:
image: traefik:v3
restart: unless-stopped
depends_on:
step-ca: { condition: service_healthy }
ports: ["80:80", "443:443", "5432:5432"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs:/certs
- ./step-ca/certs/root_ca.crt:/root_ca.crt:ro
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=traefik
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.postgres.address=:5432
- --certificatesresolvers.stepca.acme.caServer=https://step-ca:9000/acme/acme/directory
- --certificatesresolvers.stepca.acme.storage=/certs/acme.json
- --certificatesresolvers.stepca.acme.tlsChallenge=true
- --certificatesresolvers.stepca.acme.cacertificates=/root_ca.crt
networks:
traefik: { ipv4_address: 172.20.0.2 }
networks:
traefik:
external: true
Alongside it, a dnsmasq.conf that points every
*.dev.localhost name at the Traefik container, so step-ca can
reach it to validate certificates:
no-resolv
no-hosts
server=127.0.0.11
address=/dev.localhost/172.20.0.2
Then, once:
# the shared network — the static IPs above need this subnet
docker network create --subnet=172.20.0.0/24 traefik
# a password to protect step-ca's keys
mkdir -p step-ca/secrets
openssl rand -base64 32 > step-ca/secrets/password
docker compose up -d
# trust the CA so browsers accept the certificates (macOS example)
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
step-ca/certs/root_ca.crt
With that running, the app's own docker compose up joins the same
network and https://<project>.dev.localhost works with a
trusted certificate.