Skip to main content

Configuration

Protocol uses a few files to know what to do with your project. This guide explains what they are, where they live, and how they work together.

The Short Version

There are four things that configure Protocol:

  1. protocol.json — Lives in your project. Tells Protocol what Docker image to use, how to deploy, and where your config repo is.
  2. The config repo — A separate git repo next to your project. Holds your .env files, nginx configs, cron schedules — anything that changes between environments.
  3. ~/.protocol/key — Lives on each machine. The encryption key for your secrets.
  4. ~/.protocol/nodes/ — On slave nodes only. Stores per-node deployment settings that persist across blue-green directory swaps.

For most projects, you only need the first three. Node config is created automatically when you set up a slave node via protocol init.


protocol.json

This is your project's identity card. Created by protocol init, committed to git, shared across all machines.

Here's what a typical one looks like:

{
"name": "myapp",
"project_type": "php82",
"deployment": {
"strategy": "release",
"pointer": "github_variable",
"pointer_name": "PROTOCOL_ACTIVE_RELEASE",
"secrets": "encrypted"
},
"docker": {
"image": "registry/myapp:latest",
"container_name": "myapp-web"
},
"git": {
"remote": "git@github.com:org/myapp.git",
"remotename": "origin",
"branch": "master"
},
"configuration": {
"local": "../myapp-config",
"remote": "git@github.com:org/myapp-config.git"
},
"bluegreen": {
"enabled": false,
"auto_promote": false,
"health_checks": []
}
}

No credentials go in this file. It's committed to git. Docker passwords, API tokens, and encryption keys are handled through environment variables or the encrypted config repo.

Project Settings

SettingWhat it means
nameProject identifier, used for naming and lookups
project_typePHP version for the initializer: php81, php82, or php82ffmpeg

Deployment Settings

SettingWhat it means
strategy: "release"Use versioned git tags. Nodes watch a GitHub variable for the active version. Recommended.
strategy: "branch"Follow the tip of a git branch. Good for local dev. No rollback.
pointer: "github_variable"How the active release version is stored. Currently only github_variable is supported.
pointer_nameThe GitHub repository variable that stores the active release version. Default: PROTOCOL_ACTIVE_RELEASE
secrets: "encrypted".env files are encrypted in git and decrypted on arrival. Recommended for production.
secrets: "file".env files are used as-is. Fine for local dev.

Docker Settings

SettingWhat it means
imageThe Docker image to pull/push
container_nameWhich container to target for docker:exec and docker:logs
localPath to your Dockerfile source (for docker:build)

Git Settings

SettingWhat it means
remoteYour project's git remote URL
remotenameName of the git remote (default: origin)
branchThe branch to track (branch mode only)

Config Repo Settings

SettingWhat it means
localWhere the config repo lives relative to your project (default: ../myapp-config)
remoteGit remote URL for the config repo
environmentsList of environment names (branches) available

Blue-Green (Shadow) Settings

These settings control zero-downtime shadow deployments. See Shadow Deployment for the full guide.

SettingWhat it means
bluegreen.enabledEnable shadow deployment mode (true/false)
bluegreen.git_remoteGit remote URL for cloning releases (falls back to git.remote)
bluegreen.releases_dirDirectory where release clones are stored (default: sibling <project>-releases/)
bluegreen.auto_promoteAutomatically promote the shadow to production after health checks pass
bluegreen.health_checksArray of health check definitions (see below)

Health check format:

{
"bluegreen": {
"health_checks": [
{"type": "http", "path": "/health", "expect_status": 200},
{"type": "command", "command": "curl -s localhost:8080/ping", "expect_exit": 0}
]
}
}

The Config Repository

This is where the real magic happens. Your project has a sibling directory — a completely separate git repo — that stores everything specific to an environment.

/opt/
├── myapp/ ← your code (one git repo)
│ ├── protocol.json
│ ├── src/
│ ├── nginx.conf → ../myapp-config/nginx.conf (symlink!)
│ └── docker-compose.yml

└── myapp-config/ ← your configs (separate git repo)
├── .env.enc ← encrypted secrets
├── nginx.conf ← server config
├── php.ini ← PHP settings
└── cron.d/ ← cron schedules

Why a Separate Repo?

Because your production .env and your dev .env are totally different files. Your production nginx config listens on port 443 with SSL. Your dev one listens on port 80 with no SSL.

If you put these in your main repo, you'd need conditionals, templating, or a directory full of variations. With Protocol, each environment is just a branch in the config repo.

Branches = Environments

myapp-config/
└── .git/
└── refs/heads/
├── production ← production .env, nginx.conf, etc.
├── staging ← staging .env, nginx.conf, etc.
└── localhost-sarah ← Sarah's laptop .env, nginx.conf, etc.

When you run protocol config:env production, Protocol knows to check out the production branch of your config repo. When you run protocol start, it symlinks those files into your project.

What Goes Where

Type of fileWhere it livesExamples
Application secretsConfig repo (encrypted).env, database passwords, API keys
Server configurationConfig repo (plaintext)nginx.conf, php.ini, cron schedules
Docker configurationApp repodocker-compose.yml, Dockerfile
Project metadataApp repoprotocol.json
Runtime stateApp directory (gitignored)protocol.lock

Setting It Up

# Create the config repo (the wizard handles everything)
protocol config:init

# Move files from your project to the config repo
protocol config:mv .env
protocol config:mv nginx.conf

# Encrypt your secrets
protocol config:init # → choose "Encrypt secrets"

# Push to remote
protocol config:save

Switching Environments

protocol config:switch staging

This saves any changes, removes the old symlinks, switches to the staging branch, and creates new symlinks. Your app instantly has staging configs.

Docker Volume Mounting

For symlinks to work inside Docker, the config repo needs to be mounted as a volume:

services:
web:
volumes:
- '.:/var/www/html:rw'
- '../myapp-config/:/var/www/myapp-config:rw'

This way, the relative symlinks resolve correctly inside the container.


protocol.lock

This is Protocol's scratchpad. It tracks what's happening right now — which version is deployed, what processes are running, which files are symlinked.

{
"release": {
"current": "v1.2.0",
"previous": "v1.1.0",
"deployed_at": "2024-01-15T10:30:01Z"
},
"release.slave": {
"pid": 12345
},
"configuration": {
"active": "production",
"symlinks": ["/opt/myapp/nginx.conf"]
}
}

You never edit this file. Protocol manages it. It's gitignored because it's different on every machine.

The previous version is how protocol deploy:rollback knows where to go back to.


Machine-Level Config

Each machine has several things set at the Protocol level (not per-project):

Environment Name

Set once per machine:

protocol config:env production

Stored in Protocol's own config at config/config.php. This tells Protocol which config repo branch to use.

Common patterns:

  • production — live servers
  • staging — pre-production
  • localhost-sarah — Sarah's laptop
  • ci — CI/CD pipeline

Encryption Key

Set once per machine:

protocol secrets:setup "your-64-char-hex-key"

Stored at ~/.protocol/key with 0600 permissions. This is the key that decrypts your .env.enc files. Same key on every machine.

Node Config (~/.protocol/nodes/)

When a server is set up as a slave/deployment node via protocol init, its configuration is stored in ~/.protocol/nodes/<project>.json. This is separate from the project's protocol.json so that blue-green deployments can swap directories without losing track of settings.

{
"name": "myapp",
"node_type": "slave",
"environment": "production",
"repo_dir": "/opt/myapp",
"git": {
"remote": "git@github.com:org/myapp.git"
},
"deployment": {
"strategy": "release",
"pointer": "github_variable",
"pointer_name": "PROTOCOL_ACTIVE_RELEASE"
},
"bluegreen": {
"enabled": true,
"git_remote": "git@github.com:org/myapp.git",
"releases_dir": "/opt/myapp-releases",
"auto_promote": true,
"health_checks": [
{"type": "http", "path": "/health", "expect_status": 200}
]
}
}

Node config files are created with 0600 permissions and the directory with 0700. They persist across deploys and directory swaps.


Putting It All Together

Here's what happens when protocol start runs on a production node:

  1. Reads protocol.json → knows the Docker image, deploy strategy, and config repo URL
  2. Reads config/config.php → knows this machine is production
  3. Checks out the production branch of the config repo
  4. Sees .env.enc → reads ~/.protocol/key → decrypts to .env
  5. Symlinks nginx.conf, php.ini, etc. into the project
  6. Starts Docker with the decrypted secrets
  7. Starts watching for new releases

Every machine runs the same code but gets different configs because each one checks out a different branch. That's the whole trick.