The purpose of this repository is to proved a template for a base starting point for C4G course projects. This template is not mandatory, but it captures the needs of a typical C4G project that uses a website and a database. A team that uses the template for building their project can also use it to meet course reporting requirements. Features include:
-
Authentication with email/password and Google (2 test accounts provided)
-
CI/CD which will deploy the application and DB to the c4g.dev server using your applications sub-domain.
-
A user management page for
ADMINusers. Base roles areADMINandSTAFF. -
Project specific deliverable pages to quickly update as we progress through the course. Found by clicking the
Teamlink in the footer of the application. -
More to come as we add common features we find useful across applications. We welcome student pull requests to add features!
Our goal is to make this template easy to use for any student in the class, so feedback to improve the template or this readme are most welcome!
- Make sure you have the following setup and configured on your computer:
- git or Github Desktop
- NodeJS - version 24 or higher
- pnpm - Fast, disk space efficient package manager
- Docker
- Clone the repo using either SSH, HTTPS, or Github Desktop
- SSH
git clone git@github.gatech.edu:cs-6150-computing-for-good/template.git- HTTPS
git clone https://github.gatech.edu/cs-6150-computing-for-good/template.git- Get the
.envfile from Microsoft teams or ask a TA for the file. This file will be specific to your project once this repo is cloned and must be created by a TA as we have to setup the github action secrets. - Install all of the node dependencies with the following command
pnpm install- Make sure you have docker running and run the following command to initialize the database, apply all database schema, and seed some test users:
pnpm run init- If all is well up to this point your terminal should look like this:

- Next, run the development server
pnpm run devOpen http://localhost:3000 with your browser to see the result.
- You may login with either of the accounts below or with your own gmail account
| Username | Password | Role |
|---|---|---|
| c4gdevad@gmail.com | EHdqcGJajTAnSy$8 | ADMIN |
| c4gdevstaff@gmail.com | JCbSk3&&JF!h#m@x | STAFF |
- To access the database you can run the following command in a new terminal:
pnpm exec prisma studioIt should open the browser automatically or you can open http://localhost:5555/ to see the database tables.
You can start editing the page by modifying src/app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
- Replace
templatein many files to your project name. This includes theghcr.io/c4g/template-*image names indocker-compose.ymlandIMAGE_BASEin.github/workflows/publish.yaml, plus aCOOLIFY_APP_UUIDrepository variable pointing at the new project's Coolify application. - Setup oauth settings in GCP
- Setup nginx configuration, and re-run SSL cert on C4G Server.
- Generate VAPID keys for PWA setup Generator
- Generate RESEND key Resend
- Nextjs - framework
- Typescript
- Tailwind - css atomic classes
- Prisma - db type ORM system
- Prettier - formatter
- ESLint - enforce rules / policies for maintable code
- Husky - allows for code changes during local commit
- Lint-Staged - lints code on only staged files with auto-fix
- Docker - containers
- Postgres - database
- Github Actions - ci/cd process
- Nginx - server hosting configuration / routing
- Shadcn - UI component library
- RadixUI - UI component library
- Lucide-React - UI icons
- Better Auth - authentication with email/password and google
- Ag-Grid - grid / table component
- Resend - emails
If you want to contribute to this template for future projects please work with the teaching staff. We welcome any technologies that could benefit the partners and speed to delivery for features.
The application uses Docker Compose for production deployments with an automated migration workflow:
- Database: PostgreSQL 17 with persistent volume storage
- Migrations: Init container that runs database migrations before the app starts, from the same image as the app
- Application: Next.js standalone server with optimized production build
.github/workflows/publish.yaml runs on every push to main (and on manual
dispatch). It builds one image, pushes it to GHCR, and then triggers a Coolify
deployment:
ghcr.io/c4g/template:latestand:<commit-sha>
docker-compose.yml references that published image and has no build:
keys, which is what keeps the shared Coolify host from compiling the
application on every deploy — it only pulls and restarts. The deploy is
triggered from the workflow rather than by Coolify's git webhook so that
Coolify cannot pull :latest before the new image has finished uploading.
template-migrations and template-app run the same image with different
commands. The image ships the Prisma CLI (the migrator stage in Dockerfile
installs it on its own), so the migration step needs nothing extra:
template-migrations:
image: ghcr.io/c4g/template:${IMAGE_TAG:-latest}
command: ['node', '/node_modules/prisma/build/index.js', 'migrate', 'deploy']The ordering guarantee is unchanged — the app still waits on
service_completed_successfully, so it starts only after migrations exit 0.
The migration tooling is installed with npm, not pnpm, and lands at
/node_modules rather than /app/node_modules. Both details are load-bearing:
pnpm's symlink farm does not survive a COPY between stages, and the Next.js
standalone output contains symlinked packages, so copying a directory over
/app/node_modules fails with cannot copy to non-directory. /node_modules
is the last place Node looks when resolving from /app, so prisma.config.ts
still finds dotenv and prisma/config while the application's own resolution
is untouched.
A previous version built a second image from a Dockerfile.migrations that ran
pnpm install --prod — pulling Next, React and every other runtime dependency
in order to run one command. That image was 1.63 GB to carry 94 kB of
migrations. Publishing one image instead cut the total pulled per deploy from
about 2 GB to 685 MB, and halved the number of GHCR packages to keep public.
Required repository/organization configuration:
| Name | Kind | Purpose |
|---|---|---|
COOLIFY_TOKEN |
secret | Coolify API token (organization-level secret) |
COOLIFY_APP_UUID |
variable | UUID of the Coolify application to redeploy |
The deploy step skips itself when either Coolify value is missing, so a copy of this template publishes images without redeploying the template's own app.
The build itself needs no application secrets — see below.
Nothing environment-specific is baked into the image, so the same build can back
several Coolify applications. IMAGE_TAG selects which build each one runs:
leave it unset to track latest, or pin it to a commit SHA in the application's
Coolify environment variables to promote a build that has already been verified
elsewhere. Adding a test environment later is therefore just a second Coolify
application pointed at this same compose file — no repository changes, no
second image.
This requires that no NEXT_PUBLIC_* variable is present during the build.
Next.js substitutes those into the bundle only when they exist at build time, so
leaving them unset keeps process.env.NEXT_PUBLIC_* in the compiled server
output as a real runtime lookup, and each environment supplies its own value
through Coolify.
It works for NEXT_PUBLIC_VAPID_PUBLIC_KEY because that value is read
server-side only (src/lib/web-push.ts); the browser fetches the key from
GET /api/notifications/subscribe rather than reading an inlined copy. If
client code ever needs a NEXT_PUBLIC_* value directly it will be undefined
in the browser, and baking it in to fix that would re-tie the image to a single
environment — serve it from an API route or a server component prop instead.
Pull the published image and start all services:
docker compose --profile production up -dBuild the image from source instead (local verification, and what CI does):
docker compose -f docker-compose.yml -f docker-compose.build.yml \
--profile production up -d --buildCheck service status:
docker compose psView logs:
# All services
docker compose logs
# Specific service
docker compose logs template-app
docker compose logs template-migrationsStop services:
docker compose --profile production downClean shutdown with volume and orphan container removal:
docker compose --profile production down --volumes --remove-orphans- Database starts and waits for healthy status
- Migration container runs
prisma migrate deployand exits - Application starts only after migrations complete successfully
The migration container (template-migrations) runs once per deployment and automatically exits after completion. Docker Compose handles cleanup of stopped containers on subsequent deployments.
All required environment variables must be set in your .env file before deployment. See example.env for the complete list. Key variables:
DATABASE_*: PostgreSQL connection settingsAUTH_*/BETTER_AUTH_URL: Better Auth configurationNEXT_PUBLIC_VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY: Push notification keysRESEND_API_KEY: Email service configuration