The United States Environmental Protection Agency's Web Content Management System, built on Drupal 10.
# Clone repository
git clone -b main git@github.com:USEPA/webcms.git
cd webcms/services/drupal
# Start development environment
ddev start
# Complete setup
ddev aws-setup # Create local S3 bucket
ddev import-db # Import database (place .tar in .ddev/db/)
cp .env.example .env # Copy environment configuration
ddev composer install
ddev gesso install # Install theme dependencies
ddev gesso build # Build theme assets
ddev drush deploy -y # Run deployment workflow
ddev drush user:unblock drupalwebcms-adminAccess the site at: https://epa.ddev.site
- Contributing Guide - Complete setup instructions, development workflows, and deployment guide
- WARP.md - AI agent guidance for working with this repository
- Git Workflow - Branching and release process
- CI/CD Pipeline - GitLab CI configuration for automated deployments
- Deployment Workflow - Step-by-step deployment process
- Pipeline Optimizations - Performance optimization details
- Terraform Infrastructure - AWS infrastructure provisioning
- Terraform WebCMS - Application deployment configuration
- Smart Deployments - Automatic build detection analyzes changed files; deploy-only mode reduces time from 15 minutes to 3-5 minutes
- Zero-Downtime Deployments - Rolling ECS deployments with health checks
- Infrastructure as Code - Complete AWS infrastructure managed via Terraform
- Automated Testing - Security scanning with Prisma Cloud and GitLab SAST
- Multi-Environment - Separate dev, stage, and production environments
# Start local environment
cd services/drupal
ddev start
ddev gesso watch
# Make changes, then deploy to dev environment
git add .
git commit -m "feat: Your feature description"
git push origin development
# Auto-detect if build is needed (recommended)
./push-dev.sh
# Or manually override: force skip build for faster deployment
./push-dev.sh --skip-buildSee CONTRIBUTING.md for complete development guide.
- Platform: Drupal 10 on PHP 8.2
- Theme: Gesso USWDS (Pattern Lab + USWDS 3.9.0)
- Infrastructure: AWS ECS (Fargate), RDS (PostgreSQL), S3, CloudFront
- CI/CD: GitLab CI/CD with GitHub mirror
- Containers: Multi-stage Docker builds with Kaniko (drupal, nginx, drush)
- IaC: Terraform for infrastructure and application deployment
- Multi-site: English and Spanish sites share the same codebase and infrastructure; each site/language pair is its own ECS service.
- Authentication: SimpleSAMLphp service backs production SSO; local development uses the mock IdP in
services/simplesaml/so contributors can test SAML flows without external access.
Developer → GitHub (branch) → GitLab Mirror → CI/CD → AWS ECS
Branch to Environment Mapping:
development→ Dev site (automatic deployment)live→ Stage site (manual trigger, includes security scans)live→ Production (manual trigger, future)
Pipeline Stages:
- Development branch: Build → Deploy → Update (~10-15 min)
- Live branch: Build → Test → Scan → Deploy → Update (~25-35 min)
| Command | Description |
|---|---|
ddev start |
Start development environment |
ddev drush cr |
Clear Drupal cache |
ddev drush deploy -y |
Run deployment workflow (updb + cim + cr) |
ddev drush cex |
Export configuration |
ddev drush cim -y |
Import configuration |
ddev drush uli |
Generate one-time login URL |
ddev gesso watch |
Watch and rebuild theme assets |
ddev gesso build |
One-time theme build |
ddev composer phpcs |
Run PHP Code Sniffer |
ddev composer phpcbf |
Auto-fix coding standards |
ddev composer phpstan |
Run PHPStan static analysis |
Theme artifacts are intentionally not committed—after pulling any theme changes, rerun
ddev gesso build(orddev gesso watch) so CSS/JS output stays fresh.
|| Command | Description |
||---------|-------------|
|| ./push-dev.sh | Auto-detect build need & deploy to dev |
|| ./push-dev.sh --skip-build | Force skip build (fast, reuse images) |
|| ./push-dev.sh --force-build | Force full build even if not detected |
|| ./push-dev.sh --skip-build -f | Force push with skip-build |
|| ./trigger-pipeline.sh development | Manually trigger GitLab pipeline |
Automatic Build Detection:
By default, push-dev.sh analyzes changed files and automatically determines if a full build is required.
Files requiring full build (auto-detected):
- ❌
composer.jsonorcomposer.lock(module dependencies) - ❌
package.jsonorpackage-lock.json(theme dependencies) - ❌ Theme source files (
.scss,.jsinepa_theme/) - ❌
Dockerfileor build scripts - ❌
.gitlab-ci.ymlor CI/CD configuration
Files NOT requiring build (deploy-only):
- ✅ Custom modules in
web/modules/custom/ - ✅ Custom theme templates (
.twig,.php) - ✅ Configuration files in
config/ - ✅ Drush commands
- ✅ Documentation files
Manual Override Flags:
- Use
--skip-buildto force skip when you know images are compatible - Use
--force-buildto force rebuild (e.g., base image updates)
See CONTRIBUTING.md for complete command reference.
- Active System: GitLab CI (
.gitlab-ci.ymland.gitlab/docker.yml) - Deprecated: Buildkite pipelines (
.buildkite/) - retained for historical reference only - Deployment Pipeline: GitHub → GitLab Mirror → Docker Build → AWS ECS
By default, pipelines on live build images and deploy only to the stage environment. Use DEPLOY_TO_DEV=true when you need the stage build running in dev as well—for example, to reproduce a stage-only bug with dev tooling, keep dev aligned during a stage freeze, or validate infrastructure fixes before reopening development.
Steps:
- Open the GitLab pipeline form:
https://gitlab.epa.gov/drupalcloud/drupalclouddeployment/-/pipelines/new - Select the
livebranch. - Add a CI/CD variable:
- Key:
DEPLOY_TO_DEV - Value:
true
- Key:
- Click Run pipeline, then approve the manual
deploy:dev:apply-enjob when it appears.
What happens:
- The
build:drupal:stagejob builds images for bothstageanddev(dev builds are skipped unlessDEPLOY_TO_DEV=true). - Stage deploy jobs run automatically (with full Test/Scan stages).
- The dev deploy job stays manual when triggered from
live, so you must click the play button after stage validation.
Notes:
DEPLOY_TO_DEVis ignored on thedevelopmentbranch (dev deploys always happen there).- Use this flow sparingly—every dev deploy from
liverequires manual approval and will reuse thelivebranch artifacts.
Use WEBCMS_SITE_FILTER to selectively deploy to only one site when running pipelines on the live branch. This is useful for debugging site-specific issues (e.g., Spanish site configuration) or deploying emergency fixes to a single environment.
Steps:
- Open the GitLab pipeline form:
https://gitlab.epa.gov/drupalcloud/drupalclouddeployment/-/pipelines/new - Select the
livebranch. - Add a CI/CD variable:
- Key:
WEBCMS_SITE_FILTER - Value:
stage(to deploy only stage) ordev(to deploy only dev when combined withDEPLOY_TO_DEV=true)
- Key:
- Click Run pipeline.
What happens:
- When
WEBCMS_SITE_FILTER=stage: Only stage site jobs run (English and Spanish) - When
WEBCMS_SITE_FILTER=devwithDEPLOY_TO_DEV=true: Only dev site jobs run (English only) - All other site deployments, security scans, and update jobs are skipped
Common use cases:
- Spanish site hotfix: Set
WEBCMS_SITE_FILTER=stageto deploy only to stage Spanish site - Emergency stage fix: Deploy to stage without affecting dev environment
- Isolated testing: Test changes on one site before rolling out to both
Notes:
- Leave
WEBCMS_SITE_FILTERunset for normal deployments to all sites - This variable is ignored on the
developmentbranch (dev site always deploys)
webcms/
├── .gitlab-ci.yml # CI/CD pipeline configuration
├── .gitlab/ # Pipeline includes & documentation
├── services/
│ ├── drupal/ # Main Drupal application
│ │ ├── .ddev/ # DDEV configuration & commands
│ │ ├── config/ # Drupal configuration management
│ │ ├── web/ # Drupal webroot
│ │ │ ├── modules/custom/ # EPA custom modules (26+ modules)
│ │ │ └── themes/ # epa_theme (Gesso USWDS), epa_claro
│ │ ├── composer.json # PHP dependencies
│ │ └── Dockerfile # Multi-stage Docker build
│ ├── drush/ # Drush container for ECS tasks
│ ├── minio/ # Local S3 emulation
│ └── simplesaml/ # SAML authentication
├── terraform/ # Infrastructure as code
├── ci/ # CI automation scripts
├── push-dev.sh # Deploy to dev helper script
└── trigger-pipeline.sh # Manual pipeline trigger
The application includes 26+ EPA-specific custom modules in services/drupal/web/modules/custom/:
- Core functionality: epa_core, epa_workflow, epa_web_areas, epa_forms
- Content features: epa_clone, epa_content_tracker, epa_metatag, epa_node_export
- Media/Assets: epa_media, epa_media_s3fs, epa_s3fs, epa_wysiwyg
- Search/Navigation: epa_elasticsearch, epa_breadcrumbs, epa_viewsreference
- AWS Integration: epa_cloudfront, epa_cloudwatch, epa_snapshot
- Authentication: f1_sso, epa_es_auth
- Utilities: epa_alerts, epa_layouts, epa_links, epa_rss
Critical Rule: Never commit configuration in both code and database simultaneously. Always choose one source of truth.
# Export configuration after UI changes
ddev drush cex
# Import configuration when pulling changes
ddev drush cim -yMost runtime configuration lives in .env (see services/drupal/.env.example). The most important variables are:
WEBCMS_SITE– environment name (local,dev,stage,prod) used to load site-specific settings.WEBCMS_LANG– language code (en,es) so each ECS service can read the correct configs and secrets.WEBCMS_ENV_STATE– set tobuildbefore installing or importing data, and switch torunonce caches (memcached/Redis) should be enabled.
Copy .env.example to .env before editing; never commit actual secrets or site-specific values.
- Follow Drupal Coding Standards
- PHP 8.2+ features preferred
- Type hint all parameters and return values
- Run
ddev composer phpcsbefore committing - Auto-fix with
ddev composer phpcbf
- BEM naming convention for CSS
- Mobile-first responsive design
- WCAG 2.1 AA accessibility compliance
- ES6+ JavaScript (no
var) - Lint with
ddev gesso lint
Follow Conventional Commits:
feat:- New featurefix:- Bug fixdocs:- Documentationstyle:- Formattingrefactor:- Code restructuringchore:- Maintenance
Branch Strategy:
main- Stable release (matches production)live- Pre-production (deploys to stage)development- Active development (deploys to dev)feature/*,bugfix/*,hotfix/*- Feature branches
Elasticsearch Errors:
ddev poweroff
docker volume rm ddev-epa-ddev_elasticsearch
ddev start
ddev drush search-api:reindexComposer Install Fails:
rm -rf services/drupal/.ddev/vendor
ddev composer clearcache
ddev composer installDatabase Import Timeout:
ddev status # Get MySQL port
mysql -h 127.0.0.1 -P <port> -u db -pdb db < backup.sqlSkip-Build Deployment Fails:
# Run full build first to create :development-latest images
./push-dev.shSee CONTRIBUTING.md for more troubleshooting steps.
- Main site: https://epa.ddev.site
- PhpMyAdmin: https://epa.ddev.site:8036
- MailHog: https://epa.ddev.site:8025
- DDEV 1.24 or higher
- Docker Desktop
- Git
- Composer
- Node.js >=16 and npm
The United States Environmental Protection Agency (EPA) GitHub project code is provided on an "as is" basis and the user assumes responsibility for its use. EPA has relinquished control of the information and no longer has responsibility to protect the integrity , confidentiality, or availability of the information. Any reference to specific commercial products, processes, or services by service mark, trademark, manufacturer, or otherwise, does not constitute or imply their endorsement, recommendation or favoring by EPA. The EPA seal and logo shall not be used in any manner to imply endorsement of any commercial product or activity by EPA or the United States Government.