Skip to content

Development Infrastructure

The development environment follows the production deployment invariants: immutable application artifacts, no database changes during application startup, deployment through AWS Systems Manager, health-gated rollout, and automatic application rollback. Development uses shorter backup retention and host-local Redis to keep costs proportional to the environment.

docker-compose.dev.yml is the canonical EC2 development stack. Use docker-compose.local.yml for workstation development.

Runtime layout

  • Traefik terminates TLS for api.dev.dichit.money and exposes its authenticated dashboard at https://traefik.dev.dichit.money/dashboard/. Port 8080 is not published.
  • Traefik runs as root inside a hardened container because it needs read-only Docker socket access for service discovery and must read/write root-owned 0600 TLS and dashboard secret files prepared by the deployment script.
  • The application and migration images are built separately from Dockerfile.dev.
  • The application image contains compiled runtime files and production dependencies only. It does not contain environment files, source, Git metadata, tests, Prisma migrations, or development dependencies.
  • The migration image contains Prisma and migration/seed sources, but it never runs as part of application startup.
  • Redis runs on an internal Docker network and is not exposed on an EC2 host port.
  • Redis persists to the dichit-dev-redis Docker volume. The deployment script detects the Redis runtime user, repairs the volume ownership, and exports that user into Compose before Redis starts. After Redis is healthy, deployment forces a synchronous SAVE so RDB permission or disk failures fail before migrations and app rollout continue.
  • App, Traefik, and Redis use Docker's local logging driver with bounded rotation: five files of up to 20 MB per container.

AWS prerequisites

The development EC2 instance profile needs:

  • AmazonSSMManagedInstanceCore;
  • the S3 and Secrets Manager reader policy created by infra/aws/dev/deployment.yml.

Install Docker Engine, Docker Compose v2, AWS CLI, and SSM Agent on the instance. Authenticate Docker to GHCR with a read-only package credential before the first deployment.

Create one Secrets Manager secret containing the complete .env.dev payload as plaintext dotenv content. Required container-network values include:

NODE_ENV=development
PORT=3500
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=replace-with-a-random-development-secret

Do not store the dotenv payload as a JSON object; the deployment script writes the secret value directly to .env.dev with mode 0600.

Create a separate Secrets Manager secret containing an htpasswd users file for the dashboard. Generate a bcrypt entry interactively so the plaintext password does not enter Git or the Compose environment:

umask 077
dashboard_auth_file="$(mktemp)"
htpasswd -cB "$dashboard_auth_file" devadmin

aws secretsmanager create-secret \
  --region ap-south-1 \
  --name /dichit/development/traefik-dashboard-auth \
  --secret-string "file://${dashboard_auth_file}"

rm -f "$dashboard_auth_file"

The secret may contain multiple username:bcrypt-hash lines. The deployment writes it to traefik/dashboard-users with mode 0600 and mounts it read-only into Traefik. Rotate it by updating the secret value and redeploying development.

1. Create the deployment path

Reuse the account-level GitHub Actions OIDC provider if production already created it.

aws cloudformation deploy \
  --region ap-south-1 \
  --stack-name dichit-development-deployment \
  --template-file infra/aws/dev/deployment.yml \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides \
    GitHubOidcProviderArn=YOUR_GITHUB_OIDC_PROVIDER_ARN \
    DevelopmentInstanceId=YOUR_DEVELOPMENT_INSTANCE_ID \
    DockerHostRoleName=YOUR_DEVELOPMENT_INSTANCE_ROLE \
    AppEnvironmentSecretArn=YOUR_DEVELOPMENT_ENV_SECRET_ARN \
    DashboardAuthSecretArn=YOUR_DASHBOARD_AUTH_SECRET_ARN

Configure the GitHub development environment:

Kind Name Value
Secret AWS_DEV_DEPLOY_ROLE_ARN GitHubDeploymentRoleArn stack output
Variable AWS_REGION ap-south-1
Variable DEV_DEPLOYMENT_BUCKET DeploymentBucketName stack output
Variable DEV_INSTANCE_ID Development EC2 instance ID
Variable DEV_APP_ENV_SECRET_ID Development dotenv secret ARN
Variable DEV_TRAEFIK_IMAGE Immutable Traefik image digest
Variable DEV_REDIS_IMAGE Immutable Redis image digest
Variable DEV_TRAEFIK_DASHBOARD_AUTH_SECRET_ID Dashboard htpasswd secret ARN
Variable DEV_SEED_ON_DEPLOY true to seed on deploy; defaults to false
Variable DEV_SEED_DEEP_ON_DEPLOY true to pass --deep; defaults to false

Image variables must use registry digests, for example:

traefik@sha256:...
redis@sha256:...

Configure environment reviewers if development deployments require manual approval. SSH deployment secrets are no longer used.

2. Enable development backups

The development backup policy retains daily recovery points for 14 days by default and intentionally does not enable Vault Lock.

aws cloudformation deploy \
  --region ap-south-1 \
  --stack-name dichit-development-backup \
  --template-file infra/aws/dev/backup.yml \
  --capabilities CAPABILITY_NAMED_IAM

Tag the development EBS volume or other supported resources that should be protected:

Backup=dichit-development

Docker named volumes live on the EC2 storage backing /var/lib/docker; verify that the tagged EBS backup includes them and perform a restore test before relying on it.

Deployment flow

A push to dev performs the following:

  1. Builds application and migration targets from Dockerfile.dev.
  2. Publishes convenience tags plus content-addressed image digests with SBOM and provenance attestations.
  3. Uploads the Compose file, deployment scripts, and Traefik configuration to the encrypted deployment bucket.
  4. Invokes the EC2 host through SSM.
  5. Retrieves .env.dev and the dashboard htpasswd users file from Secrets Manager atomically.
  6. Resolves the Redis runtime user, repairs the Redis volume ownership, starts Redis and Traefik, verifies Redis persistence, applies migrations, optionally seeds the database, then recreates the application.
  7. Checks https://api.dev.dichit.money/health on the EC2 host and rolls the application back if it remains unhealthy.

Use the workflow's skip_deploy option to build and publish images without changing the EC2 environment. A commit containing [skip-deploy] skips the complete development release workflow.

Development deploy seeding is controlled by GitHub environment variables on the development deployment job:

  • DEV_SEED_ON_DEPLOY=true runs ./scripts/seed.dev.sh after migrations.
  • DEV_SEED_ON_DEPLOY=false skips deploy-time seeding entirely.
  • DEV_SEED_DEEP_ON_DEPLOY=true passes --deep to the seeder when seeding is enabled.
  • DEV_SEED_DEEP_ON_DEPLOY=false runs the normal development seed.

The workflow forwards those values to /opt/dichit-dev/scripts/deploy.dev.sh, so direct host runs can still override SEED_ON_DEPLOY and SEED_DEEP_ON_DEPLOY manually if needed.

Database and seed operations

Application startup never migrates, resets, or seeds the database.

Run a normal development seed on the host through an approved SSM session:

/opt/dichit-dev/scripts/seed.dev.sh

Pass --deep when the larger development dataset is required:

/opt/dichit-dev/scripts/seed.dev.sh --deep

The Reset Development Database GitHub workflow requires the exact confirmation value yes. It runs the reset inside the migration image through SSM and can optionally restart the application. It does not require SSH or Prisma tooling in the application container.

Verification after rollout

aws ssm describe-instance-information \
  --filters Key=InstanceIds,Values=YOUR_DEVELOPMENT_INSTANCE_ID

Start an approved SSM session and inspect recent container logs with:

cd /opt/dichit-dev
source .deployment-images.env
docker compose -f docker-compose.dev.yml --env-file .env.dev logs --tail 200 app traefik redis

Verify that anonymous dashboard requests are rejected and authenticated requests succeed:

curl --head https://traefik.dev.dichit.money/dashboard/
curl --user devadmin https://traefik.dev.dichit.money/dashboard/

The first request must return 401 Unauthorized. The second prompts for the password and must return the dashboard response over HTTPS.

Also verify the public health endpoint, Redis persistence after container recreation, an application rollback, and an EBS restore at least once before treating the environment as recoverable.