Skip to content

Containerize for GKE deployment - #27

Merged
markwong167 merged 1 commit into
mainfrom
claude/canopy-staging-deployment-dke07e
Aug 13, 2026
Merged

markwong167 merged 1 commit into
mainfrom
claude/canopy-staging-deployment-dke07e

Conversation

@markwong167

@markwong167 markwong167 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Multi-stage Dockerfile (Composer vendor stage → PHP-FPM runtime) with opcache tuning and php-fpm pool config fixes (clear_env=no, worker output capture, stderr logging)
  • nginx config for HTTP-facing routing (not yet wired into the image — open design question on sidecar-per-pod vs. a shared nginx tier, tracked separately)
  • /healthz endpoint for k8s liveness/readiness probes
  • CI pipeline that builds and pushes the image to Artifact Registry; the build/push logic lives in a reusable workflow (build-image.yml) so a future production pipeline can call the same logic instead of duplicating it

Split from the original scope

This PR originally carried more — multisite network config/bootstrap tooling and staging media offload config have been split out into separate, independently reviewable PRs (#43, #44) so this one stays focused on the container image and its build pipeline.

Test plan

  • vendor/bin/pest passes
  • docker build succeeds (no Docker daemon available in the sandbox this was authored in — needs a build check in CI or locally)
  • Verify the /healthz endpoint responds correctly once deployed

🤖 Generated with Claude Code

@markwong167
markwong167 force-pushed the claude/canopy-staging-deployment-dke07e branch from 8aefe5f to 8a61a95 Compare July 8, 2026 21:55
Comment thread config/application.php Outdated
Comment on lines +152 to +153
Config::define('SITE_ID_CURRENT_SITE', 1);
Config::define('BLOG_ID_CURRENT_SITE', 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: it seems to be important for the site ID to match the domain - so as we add domains this number will need to increment AND we'll need to map each site foo.gpo.ca -> 1, bar.gpo.ca -> 2, etc.

We need to do some investigation into how these two config options will play with multisite.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these are variables for wordpress multisite. As we are only doing 1 main site and multiple sub-sites, I believe that setting it to 1 is correct form.
These variables are for more complex networks that have more main sites, like say if we are somehow also handling the Green Party of Alberta's websites (I doubt we will), that's when we should dynamically define these.

Comment thread docker/nginx/default.conf
@@ -0,0 +1,38 @@
server {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: because we are already using GCLB we won't need an additional reverse proxy in staging / prod, but this MIGHT be helpful for local dev to handle request routing and so on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As PHP works a little differently, it seems like the reverse proxy here is for a layer down.

Instead of
GCLB proxy → another proxy → PHP

It is
GCLB → pod's HTTP-to-FastCGI bridge → PHP-FPM

Here is what the request path looks like:

Visitor's browser
↓ HTTPS
Google Cloud Load Balancer ← decrypts TLS, picks a container
↓ HTTP
nginx (inside the container) ← translates HTTP → FastCGI
↓ FastCGI
PHP-FPM ← actually runs WordPress

This being said, this is more difficult for me to understand, so please take what I'm saying with a grain of salt.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point about needing something to translate HTTP -> FastCGI.

Stuffing an nginx inside every application pod is probably okay in the short term but it's a bummer because it means we cannot scale them independently. If we need to add more WP instances, we have no choice but to add more nginx instances, when it's very likely that a single nginx could handle all our traffic. And putting the nginx config directly inside the docker image means we have to restart all our WP instances any time we make an nginx config change.

Anyway, it's good enough to get us started but we'll probably want to revisit this decision at some point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I wrote an issue to fix this later on:
#42

Comment thread .env.example Outdated
NONCE_SALT='generateme'

# Multisite (subdomain install)
DOMAIN_CURRENT_SITE='example.com'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably drop this in favour of using WP_HOME instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call. It's fixed on this PR. 1ca9b9f
I did leave the comment be, as it seems like someone used to dealing with wordpress multisites might look for it here.

Comment thread .env.example Outdated
# Object cache (Memorystore Redis)
REDIS_HOST='127.0.0.1'
REDIS_PORT='6379'
WP_CACHE_KEY_SALT='example.com'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably drop this all together, it only seems to be necessary in sitiuations where multiple separate WP installs are sharing the same redis server (we're not there yet).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!
I believe it's probably best to do without. staging and prod should be on different redis servers, right? Other than those two, I don't see any other WP installs in sight.
Removed:
4825a36

Comment thread config/application.php Outdated
*/
Config::define('AUTOMATIC_UPDATER_DISABLED', true);
Config::define('DISABLE_WP_CRON', env('DISABLE_WP_CRON') ?: false);
Config::define('DISABLE_WP_CRON', env('DISABLE_WP_CRON') ?: true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false ?: true is true 😬. I think what you want is null coalescing false ?? true is false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's a bug. Fixed!

Comment thread bin/setup-local.sh Outdated
if ! wp core is-installed --network 2>/dev/null; then
if ! wp core is-installed 2>/dev/null; then
echo "==> Installing WordPress"
wp core install \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we're going to run into this issue, but I believe there's a bit of a chicken and egg issue with WordPress multi-site. On a fresh database, WordPress loading with MULTISITE=true will look for wp_site and wp_blogs, which don't exist yet, and bail before wp core install can run. The usual work around is to have an env var (env('MULTISITE') ?: false) so a fresh install can come up single-site, convert, and then flip the flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude touted a better fix.
In our bin/setup-local.sh, we have multisite-convert and it said that multisite-convert "Transforms an existing single-site installation into a multisite installation".
It says that the better fix would be to do a multisite-install saying that it "Installs WordPress multisite from scratch".

I will try this fix out and report back.

Comment thread bin/bootstrap-secrets.sh Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where will this file be used? I don't think we need a .env file in k8s as the env vars will be managed by k8s.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bootstrap-secrets.sh pulls a secret payload from GCP Secret Manager and writes it to .env.local so a developer can run WordPress locally against real-shaped secrets instead of hand-typing placeholder values. (dev environment)

@IanEdington IanEdington left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks good. There's a lot going on though, I'm seeing at least 3 parts to this PR. I wonder if it would be faster to merge one piece at a time.

@@ -0,0 +1,55 @@
name: Deploy staging

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this workflow just builds and pushes an image, is the intention to have this workflow actually perform a deployment in the future? If not it might reduce confusion to rename this.

Multi-stage Dockerfile (Composer vendor stage -> PHP-FPM runtime) with
opcache tuning and php-fpm pool config fixes (clear_env=no, worker
output capture, stderr logging). Adds an nginx config for HTTP-facing
routing (not yet wired into the image - see PR discussion on
sidecar-per-pod vs a shared nginx tier) and a /healthz endpoint for
k8s liveness/readiness probes. The CI pipeline builds and pushes the
image to Artifact Registry; the build/push logic is a reusable
workflow (build-image.yml) so a future production pipeline can call
the same logic instead of duplicating it.
@markwong167
markwong167 force-pushed the claude/canopy-staging-deployment-dke07e branch from 0293e38 to 5bb7282 Compare August 11, 2026 14:57
@markwong167 markwong167 changed the title Claude/canopy staging deployment dke07e Containerize for GKE deployment Aug 11, 2026
@markwong167
markwong167 merged commit b49d9d6 into main Aug 13, 2026
@markwong167
markwong167 deleted the claude/canopy-staging-deployment-dke07e branch August 13, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants