{"id":28475247,"url":"https://github.com/pmeaney/payloadcms-052225blue","last_synced_at":"2026-04-11T19:32:48.001Z","repository":{"id":294929656,"uuid":"988536905","full_name":"pmeaney/payloadcms-052225blue","owner":"pmeaney","description":"A template ready to turn into a basic Dockerized, Github Actions CICD-deployed PayloadCMS project (e.g. blue-green)","archived":false,"fork":false,"pushed_at":"2026-01-15T17:51:43.000Z","size":2345,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-15T20:18:59.562Z","etag":null,"topics":["cicd","cms","docker","github-actions","javascript","nextjs","payloadcms","postgresql","production-ready","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pmeaney.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-22T17:35:49.000Z","updated_at":"2026-01-15T17:51:48.000Z","dependencies_parsed_at":"2025-05-30T05:30:05.636Z","dependency_job_id":null,"html_url":"https://github.com/pmeaney/payloadcms-052225blue","commit_stats":null,"previous_names":["pmeaney/payloadcms-052225blue"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pmeaney/payloadcms-052225blue","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmeaney%2Fpayloadcms-052225blue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmeaney%2Fpayloadcms-052225blue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmeaney%2Fpayloadcms-052225blue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmeaney%2Fpayloadcms-052225blue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pmeaney","download_url":"https://codeload.github.com/pmeaney/payloadcms-052225blue/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmeaney%2Fpayloadcms-052225blue/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31693272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T13:07:20.380Z","status":"ssl_error","status_checked_at":"2026-04-11T13:06:47.903Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cicd","cms","docker","github-actions","javascript","nextjs","payloadcms","postgresql","production-ready","typescript"],"created_at":"2025-06-07T14:05:47.553Z","updated_at":"2026-04-11T19:32:47.993Z","avatar_url":"https://github.com/pmeaney.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n### Todo:\n\n- Figure out how to sense for this error code 128.  on the condition if it occurs, then simply deploy.  Or, set some sort of setting of const overrideBadObj=True.  if(overrideBadObj=true) runDeploy().\n\n[gha error msg](https://github.com/pmeaney/payloadcms-052225blue/actions/runs/15433628273/job/43435841904)\n```\nQ2: Does deployment marker artifact exist? Answer: true (Last commit: 2e752db)\nfatal: bad object 2e752dbc7d1d436708dced49cb399384c85baf72\nError: Process completed with exit code 128.\n```\n\n---\n\n# Template for Blue-Green Deployment of PayloadCMS\n\n\u003eA template ready to turn into a basic Dockerized, Github Actions CICD-deployed PayloadCMS project (e.g. blue-green)\n\nThis project serves as a foundation template - dubbed the \"blue template\" - designed to be used in a basic Dockerized blue/green deployment architecture.\n\nThe naming convention `payloadcms-052225blue` follows a structured pattern: replace the date stamp and color code with the date of your new instance creation. Each deployment instance should have its dedicated repository. For example, this template can be used to create a complete blue-green deployment with separate repositories: `payloadcms052225-blue` and `payloadcms052225-green`.\n\nThis template implements a Dockerized CICD deployment of PayloadCMS and PostgreSQL applications, plus a \"remote-first, local sync\" strategy for migrations and media synchronization. For detailed GitHub repository setup instructions enabling automated CICD deployment, consult the [`Deployment Configuration Guide`](./docs-and-extras/GUIDE--Deployment-Configuration.md).\n\n\n\n## Project Components:\n\n- **PayloadCMS Application Container**\n  - Built via Dockerfile from the official PayloadCMS Template in the `./payloadcms-cms` directory, with strategic enhancements:\n    - Optimized Dockerfile\n    - Custom entrypoint script\n    - Integrated \"remote-first, local sync\" process for media and migration files, ensuring your local development environment mirrors the production server\n  - CICD pipeline automatically publishes your latest version as a containerized image to GitHub Container Registry\n    - Plus a shell script to automate secrets upload to Github Repo Secrets, prior to the initial run of the CICD process\n\n- **PostgreSQL Database Container**\n  - Deployed directly from the official PostgreSQL image without modifications\n  - The `./payloadcms-db` directory contains basic configuration\n  - Set up directly on the server during deployment\n\nBoth components are deployed through a comprehensive CICD workflow consisting of three specialized files in `.github/workflows/` - for CMS, Database, and overall orchestration.\n\n- **Migration Process**\n  - This project follows a \"Remote First, Local Sync\" environment synchronization architecture.  What this means is... When we add new content, we add it on the Remote Server first, then we pull that content down to our local environment when we want to do development work-- such as adding a new content collection. Why? So that we'll do development work in a local app environment which perfectly mirrors the remote app.\n    - When we want to do development work, it's important that we pull the newest version of the project from the remote server using the `sync-from-prod--FullStructure-andClean.sh` shell file.\n  - The `sync-from-prod--FullStructure-andClean.sh` shell file will pull the database structure \u0026 content, as well as media files, from the remote server to the local server-- Thus, synchronizing the enviornments: the local will now mirror the remote.\n  - From here, we're now ready to do some dev work.  **If that dev work is creating a new collection... then we will need to run through Payload's migration processes from the remote server.  Those steps are detailed in the [PayloadCMS-Collection-Migration-Guide.md](./docs-and-extras/PayloadCMS/PayloadCMS-Collection-Migration-Guide.md)**.\n\n**Important:** This project creates dedicated bind mount directories for media and migration files. These are created in the CICD user's home directory at:\n  - `/home/ghaCICDDevOpsUser/payloadcms-cms-052225blue__migrations`\n  - `/home/ghaCICDDevOpsUser/payloadcms-cms-052225blue__media`\n\n\nThat is to say-- since it's the Github Action's CICD Ssh User which deploys the app to `/home/\u003cyour CICD user\u003e` (e.g., `/home/ghaCICDDevOpsUser`)... the location of the migrations \u0026 media bind mount directory will be in that particular user's home dir (not your human user's-- so when you log in a as a human you'll need to navigate to `/home/ghaCICDDevOpsUser` to see the migration \u0026 media files sitting on the server). The included sync script `sync-from-prod--FullStructure-andClean.sh` retrieves these files to configure your local environment to closely match the remote server, streamlining development.\n\n# Custom Next.js Dockerfile \u0026 Entrypoint.sh Setup for PayloadCMS\n\nThis project includes enhanced Docker configuration tailored specifically for PayloadCMS deployment. The improvements are focused on practical solutions to common deployment challenges.\n\n## Dockerfile Improvements\n\nThe standard Next.js Dockerfile has been customized with several practical improvements:\n\n- **PNPM Optimization**: Switched to PNPM exclusively for faster, more reliable dependency management\n- **Flexible Build Process**: Added an option to skip Next.js build during image creation\n  - Useful when database connectivity is required during build but not available in CI/CD\n  - Allows the build to happen after container deployment when database is accessible\n- **Database Support**: Added PostgreSQL client to enable connectivity checks\n- **Environment Variable Handling**: Better management of environment variables between build stages\n- **File Organization**: Improved structure for migrations and runtime files\n\nThe modified Dockerfile introduces a multi-stage build process with a dedicated preparation stage that organizes files differently based on the selected build mode. This approach significantly improves deployment flexibility by allowing builds to be completed either during image creation or after deployment when a database connection is available. It also ensures proper file permissions and directory structures are in place, which is critical for PayloadCMS operations, especially when dealing with database migrations and content storage.\n\n## Custom Entrypoint Script\n\nThe original Docker setup used a simple start command. We've added an entrypoint script that:\n\n- Checks for required environment variables before starting\n- Waits for PostgreSQL database to become available\n- Creates and manages database migration directories\n- Intelligently detects and applies new migrations\n- Handles build completion if deferred from the Dockerfile\n- Provides helpful status messages during startup\n\nThe entryscript specifically addresses challenges with Next.js builds in the PayloadCMS context by setting the environment variable NEXT_SKIP_DB_CONNECT=true during the build process. This is a crucial workaround because when building the Docker image during CI/CD, the PostgreSQL database doesn't yet exist, but Next.js and PayloadCMS would normally try to connect to it during build time. By setting this flag, the build process effectively bypasses database connectivity requirements, allowing the image to be constructed successfully without an active database connection. This \"mock\" approach prevents build failures while still ensuring the application will properly connect to the database when actually deployed and running.\n\n## Why These Changes Matter\n\nThese modifications solve real-world challenges when deploying PayloadCMS:\n\n1. **Database Dependency**: PayloadCMS often needs database access during build, which isn't available in CI/CD\n2. **Migration Management**: Proper handling of database migrations during container startup\n3. **Improved Reliability**: Better error handling and state checking\n\nThe result is a more robust deployment process that works well in CI/CD environments while maintaining proper database connectivity and migration management.\n\n## Usage\n\nThe enhanced Docker setup works seamlessly with the included GitHub Actions workflows, allowing for both:\n\n- Complete builds during image creation (when database isn't needed)\n- Deferred builds that complete after deployment (when database access is required)\n\nThis flexibility makes the deployment process more reliable across different environments.\n\n## Deployment Options:\n\n### Remote Docker Deployment\nThe CICD files automatically handle remote Docker deployment once you configure the A. Repo, B. Repo Secrets, and C. the various templated placeholder names, as detailed in the [`Deployment Ce`](./docs-and-extras/GUIDE--Deployment-Configuration.md).\n\nYou'll want to clone the project, follow my terraform deployment guide, then follow the Deployment Configuration Guide.\n\n### Local Docker Development\n\n1. Clone the project\n2. Launch the local development environment with this command.  It spins up in the background, then tails the logs.\n```bash\ndocker compose -f docker-compose.local.yml up -d \u0026\u0026 docker compose -f docker-compose.local.yml logs -f\n```\n\n### Helpful commands\n\n- Connect to postgresql, once sshed into server (uses default credentials):\n  - `docker exec -it payloadcms-db-052225blue psql -U payloadcms-052225blue-user -d payloadcms-db-052225blue`\n\n### Remote Deployment\n\nFor Remote Deployment, see\n\n### Local Docker After Remote Development\n\nThis project follows a **Remote First, Local Sync** strategy.\n\nWhich means... to develop a project on the template, you'll need to reploy it to a remote server (1cvpu, 2gb ram minimum).  From there, you can run the `./payloadcms-cms/sync-from-prod.sh` script, which will pull the migration \u0026 media directories from the live remote server... down to your local laptop.  From there, you can run the Local Docker Development command `docker compose -f docker-compose.local.yml up -d \u0026\u0026 docker compose -f docker-compose.local.yml logs -f` and your local environment at `localhost:3000` on your browser should mimic what you see at your live webiste.  It populates your local project with the same setup (database schema \u0026 data, and media files) that the project has on your remote server.\n\n\n## Resources\n\n- Original Repo, where I figured out a deployment methodology: \n  - [template-payloadcms-portfolio2025](https://github.com/pmeaney/template-payloadcms-portfolio2025)\n- [PayloadCMS's Website Template](https://github.com/payloadcms/payload/tree/main/templates/website)\n\n## CICD Workflow\n\n```mermaid\nflowchart TB\n    GitPush[/\"Push to main branch\"/] --\u003e MainWorkflow[\"z-main.yml\n    Main Deployment Pipeline\"]\n    \n    MainWorkflow --\u003e DBCheckInit\n    \n    subgraph \"Database Pipeline\"\n        DBCheckInit[\"a-db-init.yml\n        Database Check \u0026 Init\"]\n        DBCheckInit --\u003e CheckDBExists{\"DB Container\n        Exists?\"}\n        CheckDBExists --\u003e|No| CreateDB[\"Create PostgreSQL Container\n        - Create volumes\n        - Configure networks\n        - Set environment vars\"]\n        CheckDBExists --\u003e|Yes| SkipDB[\"Skip Database Setup\"]\n    end\n    \n    CreateDB --\u003e CMSFECheck\n    SkipDB --\u003e CMSFECheck\n    \n    subgraph \"Frontend Pipeline\"\n        CMSFECheck[\"b-cms-fe-check-deploy.yml\n        Frontend Check \u0026 Deploy\"]\n        CMSFECheck --\u003e DownloadMarker[\"Download Last\n        Deployment Marker\"]\n        DownloadMarker --\u003e DetectChanges[\"Check PayloadCMS\n        Directory Changes\"]\n        DetectChanges --\u003e ChangesExist{\"Changes\n        Detected?\"}\n        \n        ChangesExist --\u003e|Yes| BuildPublish[\"Build \u0026 Publish\n        Docker Image\"]\n        BuildPublish --\u003e DeployFE[\"SSH to Server \u0026\n        Deploy Frontend\n        (step-deploy--cms-fe)\"]\n        DeployFE --\u003e SaveMarker[\"Save Deployment\n        Marker Artifact\"]\n        \n        ChangesExist --\u003e|No| SkipFE[\"Skip Frontend\n        Deployment\"]\n    end\n    \n    subgraph \"Docker Build Process\"\n        BuildPublish --\u003e DockerBuildStages[\"Multi-stage Dockerfile\"]\n        \n        subgraph \"Build Stages\"\n            subgraph \"Base \u0026 Dependencies Setup\"\n                DockerBuildStages --\u003e BaseImage[\"Base Stage\n                - node:20-alpine\n                - Install pnpm@10.3.0\"]\n                BaseImage --\u003e DepsStage[\"Dependencies Stage\n                - Add libc6-compat\n                - Copy package.json \u0026 lock file\n                - pnpm install --frozen-lockfile\"]\n                DepsStage --\u003e BuilderStage[\"Builder Stage\n                - Copy dependencies from Dependencies Stage\n                - Copy all source files\n                - Copy ENV_FILE to .env\n                - Set SKIP_NEXTJS_BUILD flag\"]\n            end\n            \n            subgraph \"Build Decision \u0026 Execution\"\n                BuilderStage --\u003e SkipNextBuild{\"SKIP_NEXTJS_BUILD\n                = true?\"}\n                SkipNextBuild --\u003e|Yes| PrepareRuntime[\"Prepare for Runtime Build\n                - Create minimal .next\n                - Set skip-build flag\n                - Copy src, config files\"]\n                SkipNextBuild --\u003e|No| RunBuild[\"Run Full Build\n                - pnpm run ci\n                - Build Next.js app\"]\n            end\n            \n            subgraph \"Runtime Preparation\"\n                PrepareRuntime --\u003e RuntimePrep[\"prepare-runtime Stage\n                - Copy env vars, public files\n                - Copy config files\n                - Copy entrypoint.sh\n                - Copy src/migrations\"]\n                RunBuild --\u003e RuntimePrep\n                \n                RuntimePrep --\u003e RuntimeBuildCheck{\"SKIP_NEXTJS_BUILD\n                = true?\"}\n                RuntimeBuildCheck --\u003e|Yes| CopySourceFiles[\"Copy entire src dir\n                for runtime build\"]\n                RuntimeBuildCheck --\u003e|No| CopyBuildOutput[\"Copy built .next\n                artifacts\"]\n                \n                CopySourceFiles --\u003e RunnerStage\n                CopyBuildOutput --\u003e RunnerStage\n            end\n            \n            subgraph \"Final Image\"\n                RunnerStage[\"Runner Stage\n                - Set NODE_ENV=production\n                - Install postgresql-client\n                - Add nextjs user/group\n                - Copy prepared files\n                - Set file permissions\n                - Expose port 3000\"]\n                RunnerStage --\u003e FinalDockerImage[/\"Docker Image\n                ghcr.io/pmeaney/portfolio-payloadcms:latest\"/]\n            end\n        end\n    end\n    \n    subgraph \"Deployment Process\"\n        DeployFE --\u003e SSHToServer[\"SSH to Production Server\"]\n        SSHToServer --\u003e AuthGHCR[\"Authenticate with\n        Container Registry\"]\n        AuthGHCR --\u003e CreateEnvFile[\"Create Prod Env File\n        from PAYLOAD__SECRET_ENV_FILE\"]\n        CreateEnvFile --\u003e PullImage[\"docker pull\n        ghcr.io/pmeaney/portfolio-payloadcms:latest\"]\n        PullImage --\u003e RemoveOldContainer[\"docker rm -f\n        payloadcms-cms-portfolio-prod\"]\n        RemoveOldContainer --\u003e RunContainer[\"docker run\n        - Set container name\n        - Connect to postgres network\n        - Connect to main network\n        - Map port 3000\n        - Inject env vars\"]\n        \n        FinalDockerImage -.-\u003e PullImage\n    end\n    \n    subgraph \"Container Runtime\"\n        RunContainer --\u003e EntrypointScript[\"entrypoint.sh Execution\"]\n        \n        EntrypointScript --\u003e EnvCheck[\"Environment Checks\n        - Verify DATABASE_URI\n        - Verify PAYLOAD_SECRET\"]\n        EnvCheck --\u003e ParseDBParams[\"Parse Database\n        Connection Parameters\n        from DATABASE_URI\"]\n        ParseDBParams --\u003e WaitForDB[\"Wait for PostgreSQL\n        (30 attempts with 3s delay)\n        using pg_isready\"]\n        \n        WaitForDB --\u003e RunMigrations[\"Run Database Migrations\n        pnpm run payload:migrate\"]\n        RunMigrations --\u003e CheckSkipBuildFlag{\".next/skip-build\n        file exists?\"}\n        \n        CheckSkipBuildFlag --\u003e|Yes| BuildNextJS[\"Build Next.js\n        NEXT_SKIP_DB_CONNECT=true\n        pnpm run build\"]\n        CheckSkipBuildFlag --\u003e|No| StartApp[\"Start Next.js Application\n        pnpm run start\"]\n        BuildNextJS --\u003e StartApp\n    end\n    \n    subgraph \"Verification \u0026 Summary\"\n        DeployFE --\u003e WaitPeriod[\"Wait Period (4 min)\"]\n        WaitPeriod --\u003e CheckContainers[\"docker ps -a\"]\n        CheckContainers --\u003e CheckLogs[\"docker logs\n        \u003ccontainerId\u003e\"]\n        \n        SkipDB --\u003e DeploySummary[\"Generate Deployment\n        Summary\"]\n        CreateDB --\u003e DeploySummary\n        SkipFE --\u003e DeploySummary\n        SaveMarker --\u003e DeploySummary\n        DeploySummary --\u003e MarkdownReport[\"Create Markdown Report\n        - Commit details\n        - Database status\n        - Frontend changes\n        - Action taken\"]\n    end\n    \n    classDef workflow fill:#f9f,stroke:#333,stroke-width:2px\n    classDef process fill:#bbf,stroke:#333,stroke-width:1px\n    classDef decision fill:#fbb,stroke:#333,stroke-width:1px\n    classDef container fill:#bfb,stroke:#333,stroke-width:1px\n    classDef dockerstage fill:#9de,stroke:#333,stroke-width:1px\n    classDef entrypoint fill:#bfd,stroke:#333,stroke-width:1px\n    \n    class MainWorkflow,DBCheckInit,CMSFECheck workflow\n    class DetectChanges,BuildPublish,DeployFE,RunContainer process\n    class CheckDBExists,ChangesExist,CheckSkipBuildFlag,SkipNextBuild,RuntimeBuildCheck decision\n    class StartApp container\n    class BaseImage,DepsStage,BuilderStage,PrepareRuntime,RunBuild,RuntimePrep,RunnerStage,CopySourceFiles,CopyBuildOutput dockerstage\n    class EntrypointScript,EnvCheck,ParseDBParams,WaitForDB,RunMigrations,BuildNextJS entrypoint\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmeaney%2Fpayloadcms-052225blue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmeaney%2Fpayloadcms-052225blue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmeaney%2Fpayloadcms-052225blue/lists"}