Building a secure deployment pipeline is often seen by the developers as a hassle and an expensive process. In reality, many developers and small teams use unsafe methods such as copying files over SSH, by exposing port 22 to the internet or storing long-lived credentials in build pipelines. While these approaches are convenient, they introduce unnecessary security risks that can compromise production environments.
This article leverages Google Cloud's FREE tier and BitBucket, Cloudflare FREE accounts to build Zero-Trust Zero-Cost Production Deployment Architecture.
1. Vulnerabilities with "Convenient" Deployment Methods
Logging into the server and running git pull && composer install is one of the quick, easy, and convenient ways to deploy an application to production and see the application sync up with the repository. Another convenient way to deploy is to copy the files over SSH.
But when we look past the convenience, we can clearly see the underlying threats that are waiting to be exploited.
When a web application's deployment infrastructure is set up to pull code directly from source or allow direct administrative access, the following 3 vulnerabilities are almost always introduced.
Open Port 22
Developers or System Admins open port 22 to the internet to SSH into the production VMs or to allow third-party applications to copy files into the production VM. The moment this is done, the server becomes a target. Threat vectors, such as hackers and malicious bots, can target the IP with credential stuffing attacks, looking for weak passwords.
Installing Git and Build Tools in Production
Installing Git in a production environment is a major liability that can lead to the leaking of the source code. A small misconfiguration in Apache or Nginx configuration can expose the hidden .git directory to the public web. This will expose malicious actors to download the entire source code history, structure and database schemas.
Furthermore, running resource-heavy commands like composer install or npm install directly on production environments forces the server to download third-party packages and run scripts directly on the production server. If any of these packages are compromised, the production environment will be a victim of a supply-chain attack.
Moreover, if the production server is using low-spec e2-micro or e2-small VMs, running resource-heavy compilation processes can spike CPU utilisation, deplete memory and crash the server, making the production application unavailable.
Hardcoded Secrets Leak
To authenticate the live server with the code repositories like GitHub, BitBucket, GitLab, etc., developers often create unencrypted SSH keys or API tokens directly on the VM hard drive, or WORSE, commit them into the repository itself. If the application suffers an exploit such as Local File Inclusion (LFI), these secret files are the first thing that the attacker will look for and exfiltrate, which will lead to a minor application bug into a catastrophic full-scale breach.
2. The Blueprint: Decoupling Build from Runtime
To prevent the vulnerabilities listed above in the " Vulnerabilities with 'Convenient' Deployment Methods" section, we have to separate the runtime environment from the building environment. The production server has only one job, which is to serve the application to users as fast and securely as possible. It should never be tasked with compiling code, downloading third-party packages or holding on to long-lived access keys.
Below is a high-level architecture of the production environment setup, mapping out the pipeline and network setup
- Git Commit and Push:- Developer commits and pushes the code or merges the code to the
masterbranch in the Git repository. - Webhook Trigger:- Git Repository intercepts the push and fires the webhook, signalling Google Cloud Build trigger to fire up the serverless build environment container.
- Secret Injection and Compilation:- Google Cloud Build fetches the SSH private key stored in Google Cloud Secret Manager securely and clones the repository, and runs
composer install --no-dev --optimize-autoloader --no-scripts --ignore-platform-reqsto optimise production dependencies and remove unnecessary development vulnerabilities like.gitdirectory. - "Golden Artifact" Creation:- The sterilised project directory is compressed into a single tarball and renamed with a unique build ID. This tarball is uploaded into a separate lockdown Google Cloud Storage(GCS) bucket and terminates the Build container.
- Gateway Access (IAP):- To deploy this tarball into the production environment, the Administrator has to SSH into the production VM using Identity-Aware Proxy (IAP). This eliminates the need for SSH keys in the production environment and public-facing open SSH (22) port. Also, this gives the Administrator control over who can SSH into the production environment using Google Cloud Identity and Access Management(IAM).
- Atomic Deploy:- In the production VM, the administrator runs local
deploy.shscript through the console. The script securely fetches the latest tarball from the Google Cloud Storage(GCS) bucket and extracts it to an isolated release directory, and swaps the symlink pointing the server to the new code with zero downtime. - Network Isolation:- The production server is placed in an isolated Virtual Private Network(VPC) with strict firewall rules that deny everything except SSH(port 22) traffic coming from Google IAP IP range and HTTP(port 80), HTTPS(port 443) from Cloudflare IP ranges.
Keeping the build environment separate, it ensures that one compromised environment will not bleed into the other and greatly reduces the blast radius.
3. Isolated Builds & Secret Injection (Webhook + Cloud Build)
First line of defence to a zero-trust architecture deployment is to ensure the security of the code and build process. Instead of relying on GCP's native repository connectors or committing build configuration files into the repository, this architecture uses Google Cloud Build Webhook Trigger combined with cryptographic keys configured entirely inline in Google Cloud.
Webhook-Based Build Trigger
Webhook is used to kick off the build pipeline without manual intervention. Webhook allows bypassing the repository integration entirely.
Inside GCP Cloud Build, we can configure a trigger and configure it as a "Webhook event", which will generate a unique and protected webhook URL containing a cryptographic secret token.
Repository Authentication Using SSH Keys
Because the build container starts completely empty, it must authenticate with the repository to clone the code base. Since hardcoding the keys in the configuration is out of the question, we can offload the key to Secret Manager. The SSH private key is stored in Google Secret Manager. As the second guardrail, we create a new dedicated service account for the build process, granting only necessary IAM roles such as roles/secretmanager.secretAccessor.
Inline Cloud Build Configuration
Instead of committing the build configuration to the Git repository, we select the inline option in the Cloud Build Trigger settings. This script handles everything from accessing the secrets from Secret Manager to creating the tarball and uploading it to the Google Cloud Storage(GCS) bucket.
steps:
- name: gcr.io/cloud-builders/git
entrypoint: bash
args:
- '-c'
- >-
# This checks if the branch is 'master'. If not, the build
# process terminates, helping to prevent unnecessary builds.
if [ "$_BRANCH" != "master" ]; then
echo "Push detected on branch [$_BRANCH]. Skipping execution since it is not master."
exit 0
fi
echo "Verified push to master branch! Proceeding with clone..."
mkdir -p ~/.ssh
echo "$$SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no"
git clone --depth 1 git@bitbucket.org:[your-bitbucket-username]/[your-repository-name-here].git .
secretEnv:
- SSH_KEY
- name: composer:2
args:
- install
- '--no-dev'
- '--optimize-autoloader'
- '--no-scripts'
- '--ignore-platform-reqs'
- name: ubuntu
entrypoint: bash
args:
- '-c'
- >-
rm -rf .git
# Using entrypoint + args forces Cloud Build to inject the ID string directly.
tar -czvf /tmp/release-$BUILD_ID.tar.gz .
mv /tmp/release-$BUILD_ID.tar.gz .
options:
logging: CLOUD_LOGGING_ONLY
artifacts:
objects:
location: gs://[your-secure-google-cloud-storage-bucket-name-here]/releases/
paths:
- release-$BUILD_ID.tar.gz
availableSecrets:
secretManager:
- versionName: >-
projects/[your-google-cloud-project-id-here]/secrets/bitbucket-ssh-key/versions/latest
env: SSH_KEYThis inline configuration-driven architecture ensures that, in any case, the code base is compromised, the attacker will not be able to see any of the Google Cloud Storage(GCS) bucket names, pipeline flow architecture or project IDs.
4. Locking Down the Network (Cloudflare + IAP)
Writing secure application code is not enough for a zero-trust architecture if your production server is wide open to the public internet. The firewall should be configured to Deny by Default posture and allow traffic only for allowed sources. This ensures that even if an attacker discovers the production VM's public IP address, they will not be able to access it.
To enforce perimeter hardening without incurring enterprise firewall costs, this phase sets up a two-pronged defence: Cloudflare acts as the only public gateway for web traffic, while GCP Identity-Aware Proxy (IAP) provides a completely stealth management layer for administration.
Ingress Protection (Edge Isolation with Cloudflare)
Many developers believe installing an SSL certificate and routing traffic through Cloudflare makes the app or website secure. However, if the GCP firewall allows traffic from 0.0.0.0/0(everyone), the server remains exposed. Attackers scanning public cloud IP ranges can find the VMs IP address and open ports, bypassing Cloudflare.
To prevent this origin bypass, we have to establish GCP firewall rules to allow only the HTTP and HTTPS traffic originating from Cloudflare IP ranges.
Administrative Access: Closing Port 22
The traditional way to administer a VM is to keep port 22 open and use SSH keys to log in. This method is not secure and invites brute force attacks against port 22.
This architecture does not open port 22 to the public internet. Instead, we leverage serverless Identity-Aware Proxy (IAP) TCP Forwarding by opening port 22 for the traffic originating only from the Google Identity-Aware Proxy (IAP) IP range 35.235.240.0/20.
To log in to the server, we route the traffic exclusively through Google's secure internal proxy range by running gcloud compute ssh project-nullbyte-vm --tunnel-through-iap in Google Cloud Shell.
This architecture is security-first because No open admin management ports to attempt brute-force attacks (completely closed to outside), Centralized access logs because authentication is directly tied to Google Cloud IAM layer, which logs every login attempt and offers total visibility for administration, Zero Infrastructure Overhead because it eleminates the need to run an allways-on bastion host or separate management VPN server.
5. Hardening the Host & The Atomic Swap
To complete the secure architecture, securing the build environment and network is not enough. True zero-trust security requires host-level hardening. If a malicious actor gains access to the production server (maybe via a Remote Code Execution(RCE) vulnerability of the application code), we need to make sure the host operating system and identity architecture restricts their lateral movement.
Hardening the Host Identity: Least-Privilege IAM
By default, when provisioning a GCP VM, it attaches the GCP Compute Engine default service account. Leaving this service account attached to the VM is a major vulnerability, as this service account has excessive permissions. If an attacker gains access to the VM, they can query the internal Instance Metadata Service (IMDS) to steal the service account token and take over the entire GCP cloud project.
To eliminate this vulnerability, we need to harden the host VM identity by isolating it by creating a dedicated service account for the VM and granting only the minimum required privileges. Furthermore, we restrict this role via IAM bindings so it can only read from our specific deployment bucket. It possesses zero permissions to write, delete, or read from any other bucket in the system.
Master Script
Because the administrator uses Identity Aware Proxy(IAP) to log in securely, the administrator can run the localized deployment script directly to execute the deployment. Script pulls down the "Golden Artifact" tarball from Google Cloud Storage(GCS) and unpacks it, and completes an instantaneous symlink swap.
#!/bin/bash
# SECURE SYMFONY DEPLOYMENT SCRIPT
# abort if any command fails
set -e
BUCKET_NAME="your-secure-storage-bucket-name"
RELEASE_DIR="/var/www/releases"
SHARED_DIR="/var/www/shared"
LIVE_LINK="/var/www/html"
echo "Finding latest build artifact..."
LATEST_ARTIFACT=$(gcloud storage ls gs://$BUCKET_NAME/releases/*.tar.gz | sort | tail -n 1)
if [ -z "$LATEST_ARTIFACT" ]; then
echo "No artifacts found in bucket!"
exit 1
fi
FILENAME=$(basename "$LATEST_ARTIFACT")
TIMESTAMP=$(date +%Y%m%d%H%M%S)
NEW_RELEASE="$RELEASE_DIR/release_$TIMESTAMP"
echo "Deploying $FILENAME to $NEW_RELEASE..."
sudo mkdir -p "$NEW_RELEASE"
sudo gcloud storage cp "$LATEST_ARTIFACT" /tmp/$FILENAME
sudo tar -xzf /tmp/$FILENAME -C "$NEW_RELEASE"
sudo rm /tmp/$FILENAME
# Symlink the shared environment file
sudo ln -sf "$SHARED_DIR/.env.local" "$NEW_RELEASE/.env.local"
# --- HARDENED PERMISSIONS ---
# Root owns the files, www-data can only read them
sudo chown -R root:www-data "$NEW_RELEASE"
sudo find "$NEW_RELEASE" -type f -exec chmod 640 {} \;
sudo find "$NEW_RELEASE" -type d -exec chmod 750 {} \;
# Symfony needs WRITE access ONLY to var/cache and var/log
sudo mkdir -p "$NEW_RELEASE/var/cache" "$NEW_RELEASE/var/log"
sudo chown -R www-data:www-data "$NEW_RELEASE/var/cache" "$NEW_RELEASE/var/log"
# ------------------------------------------
echo "Running Symfony tasks..."
# Note: Because --no-scripts is used when running composer in build process, we must clear/warm up cache manually
sudo -u www-data php "$NEW_RELEASE/bin/console" cache:clear --env=prod || exit 1
sudo -u www-data php "$NEW_RELEASE/bin/console" cache:warmup --env=prod || exit 1
# running database migrations
sudo -u www-data php "$NEW_RELEASE/bin/console" doctrine:migrations:migrate --no-interaction || exit 1
echo "Swapping symlink (Zero Downtime)..."
sudo ln -sfn "$NEW_RELEASE" "$LIVE_LINK"
sudo systemctl reload [server-application-name]
echo "Deployment Successful!!!"
# Clean up old releases (keeps the 5 most recent to save disk space)
cd $RELEASE_DIR && sudo ls -1tr | head -n -5 | xargs -d '\n' rm -rf --Since the script is run by an administrator and restricts the directory ownership to root, even if an attacker gains access to the server, they can not edit or replace the program files. Furthermore, in the event of a failure, the administrator can easily swap the symlink to the previous version of the application to roll back.
6. The Financial Blueprint: Enterprise Security for $0
When setting up production environments, developers are often forced to choose between robust security and financial feasibility. Standard enterprise security architectures demand expensive dedicated firewalls, high-tier CI/CD platform licensing, physical key management hardware, and costly management VPN infrastructures.
By strategically using the generous Always Free tiers of Google Cloud Platform and leveraging modern edge networks, the entire zero-trust deployment and security pipeline runs at exactly $0 per month.
While you still pay standard baseline costs for your core compute engine VM and managed database tier, the entire automated pipeline and perimeter defense layers add absolutely zero financial overhead to the operational bill.
+---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Component | Security Responsibility | Allocation / Free Tier Quota | Operational Cost | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Google Cloud Build | Ephemeral, serverless package compilation | 120 free build minutes per day. | $0.00 | | | and artifact creation. | A standard production build takes ~2 minutes.| | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Google Secret Manager | Secure, out-of-band vaulting and dynamic | 6 active secrets and 10,000 access requests | $0.00 | | | injection of the Bitbucket private SSH | per month free. We utilize exactly 1 active | | | | deploy key. | secret slot. | | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Google Cloud Storage | Private, non-public vault holding the | 5 GB of Standard Storage per month free | $0.00 | | | immutable "Golden Artifact" tarball. | within regional constraints. An application | | | | | tarball is typically <50 MB. | | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Identity-Aware Proxy | Bastionless, encrypted administrative SSH | Completely free for TCP forwarding | $0.00 | | (IAP) | access over Google's internal networks. | capabilities natively managed by GCP IAM. | | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+ | Cloudflare (Free Tier) | Edge DNS routing, automatic SSL encryption, | Completely free core proxying features | $0.00 | | | global DDoS protection, and traffic filtering.| sufficient for origin whitelisting defenses. | | +---------------------------+-----------------------------------------------+----------------------------------------------+-------------------+
Final Thoughts: Production Security on a Small Budget
Security is a mindset, not a line item on an invoice. True zero-trust engineering isn't about buying expensive software suites; it is about designing your environment so that no element inherently trusts another, every access privilege is explicitly limited, and your attack surface is reduced to the absolute bare minimum.
By separating the build tasks from the live server, locking down your network to trusted proxy ranges, and managing credentials inside isolated cloud vaults, you gain the exact same peace of mind enjoyed by major enterprise environments - all while keeping your budget focused entirely on your core application.