Staging to production, one image
The image that reaches production should be the one you looked at, not one built again from the same commit. So staging gets a home of its own first, and after that every release is two commands with one tag between them.
Before you start
The project is live in production, the way First deploy to a fresh VPS leaves it. You work on the machine that registered it, from the project directory, and your login may push images.
$ grim env:pull production
$ rm .env.production
$ grim auth:whoamiStaging here shares the server with production. It gets its own directory, database, vhost and certificate, so the two never touch. A second server works the same way: raise it and give it its token as in the first deploy, then use its alias wherever this page says vps1.
The local stack is up and the suite passes. The release in step four runs the tests before it builds.
The ritual, in order
Seven steps. The first three are done once and set staging up. The last four are the flow you repeat, and only the sixth touches production.
Point the staging name at the server
The certificate for staging is issued the moment the environment is registered, and Let's Encrypt has to find the server behind the name. Create the
Arecord at your DNS provider and wait until it answers.$ dig +short A staging.shop.example.com 203.0.113.10An empty answer means the record has not spread yet. Go on only when the address is there.
Give staging a home
Run
grim server:add-projectagain from the project directory, with the staging domain and--env=staging. Production is not touched.$ grim server:add-project shop --host=vps1 --domain=staging.shop.example.com --env=stagingIt creates
/opt/shop-staging, the database and usershop_staging, the vhostgrim-shop-staging.conf, a certificate and a fresh.envwith its ownAPP_KEY. In the summary theEnvironmentrow saysstaging,All envslistsproduction, staging, andNextisgrim release staging. Read theSSLrow before you go on.faileddoes not stop the run.Name both environments in grim.json
grim.jsonprobably carriesdeploy.hostanddeploy.domainfrom the first deploy. The top-level domain is production's and staging never inherits it. Without an entry of its own, staging takes its domain from your server registration in~/.grim/servers.json, which a colleague's machine and the CI runner do not have. Move both keys underenvironments, one entry per environment, leave the rest of thedeployblock as it is, and commit the file."deploy": { "environments": { "production": { "host": "203.0.113.10", "domain": "shop.example.com" }, "staging": { "host": "203.0.113.10", "domain": "staging.shop.example.com" } } }Set no
pathhere. Production lives in/opt/shopand staging in/opt/shop-staging, and both are found without it. Then letgrim env:pullprove that the file resolves staging.$ grim env:pull staging $ grep -E '^(APP_URL|DB_DATABASE)=' .env.staging APP_URL=https://staging.shop.example.com DB_DATABASE=shop_staging $ rm .env.stagingRelease to staging
grim releasewith the environment named picks the next tag, runs the suite, builds the image, pushes it and deploys it to staging. Always name the environment. Without an argument the target isproduction.$ grim release staging Releasing shop@v1.4.3 … Deploy shop@v1.4.3 to 203.0.113.10 (staging)?Note the tag from the first line. It is also the
Tagrow of the deploy summary. The deploy runs the hooks fromgrim.jsonon staging, so the migrations meet a real MySQL here before they meet production.The first deploy to staging creates
admin@staging.shop.example.comwith a random password. It is shown once, in theAdminandPasswordrows of that deploy's summary. Copy it before the terminal scrolls away.Look at it
Ask the site, read what the app logged while it started with
grim logs, then click through the change in a browser. Staging has its own database, so what you create here stays here.$ curl -I https://staging.shop.example.com/api/v1/healthcheck $ grim logs app --remote=stagingWhen something is wrong, fix it, commit and repeat step four. The next tag is
v1.4.4, and that is the one that goes on.Send the same tag to production
grim deploybuilds nothing. It tells production to pull the tag that staging is running and to restart on it.$ grim deploy production --tag=v1.4.3 Deploy shop@v1.4.3 to 203.0.113.10 (production)?The server pulls the image, restarts the containers, waits for the app to report healthy and runs the same hooks, migrations included.
✓ Deployed shop@v1.4.3 to 203.0.113.10is the last line.Check production
Look at the live site the same way you looked at staging.
$ curl -I https://shop.example.com/api/v1/healthcheck $ grim logs app --remote=productionWhen the release turns out badly, Roll back a deploy is the way back.
What you have now
Two stacks of one project, side by side, behind the same nginx and Traefik.
/opt/shop/ production, database shop
/opt/shop-staging/ staging, database shop_stagingEach has its own .env and its own .deploy-history. grim env:set, grim env:pull and grim env:push take the environment as their argument. The remote:* commands and grim server:auth take none and act on the environment that was registered first, which is production.
grim.json names both targets, so a colleague whose key the deploy user accepts can run steps four to seven without registering anything. From now on a release is grim release staging, a look, and grim deploy production --tag=<tag>.
When it does not work
Staging answers on the production domain
deploy.environments.staging.domain names the production hostname, or staging was released by a grim older than 1.0, which handed the top-level deploy.domain to every environment. Two stacks then claim one hostname. Fix deploy.environments.staging.domain, then deploy the current tag again to both environments so that each compose file is rendered with its own domain.
“Domain "staging.shop.example.com" is already served by another project's nginx vhost on 203.0.113.10:”
Staging used to be a project of its own, such as shop-staging. Retire it first with grim server:remove-project shop-staging --keep-ssl --keep-db, then repeat step two.
The staging site answers without a certificate
The SSL row said failed. The DNS record was not visible yet. Wait until dig shows the address, then run the certbot command that was printed under the summary.
“Image not found. Build it first:”
Production could not pull the tag. Compare it with the Releasing shop@… line from step four. A tag typed from memory is the usual cause.
“Environment 'staging' not configured for project 'shop'.”
grim logs --remote reads only ~/.grim/servers.json, and this machine never registered staging. Releases and the env:* commands work from grim.json alone. For logs, work from the machine that ran step two.
Questions
Can I put a password in front of staging?
Not with grim server:auth. It takes no environment and acts on the one registered first, so on this project it would lock production. Keep staging on a name nobody guesses, or protect it inside the app.
Does staging get a copy of production's data?
No. shop_staging starts empty and the first deploy migrates it. Moving data between the two is your own dump and import.
Next recipe
Roll back a deploy
Read the deploy history, put the server back on the previous tag, check the site, and deploy an older tag by name when one step back is not enough.
Read itSpells used here
grim server:add-project
Give a project a home
Prepare one environment of a project on a production server, with its database, nginx vhost, certificate, compose stack and .env.
grim release
Ship in one pass
Test, build, push and deploy the project to an environment with a single command and a single tag.
grim deploy
Send an image to a server
Roll a pushed image tag out to one environment over SSH, verify the app answers, and fall back to the previous tag when it does not.
grim env:pull
Fetch a server's .env
Copy the .env of a deployed environment into the project as .env.<environment>, to read it or to edit and push back.
grim logs
Read the logs
Show what the containers have been printing, for one service or all of them, on your machine or on a deployed server.
Related recipes
First deploy to a fresh VPS
Take a bare Ubuntu or Debian server and a project that runs on your machine, and end with the project live on its own domain over HTTPS.
Roll back a deploy
Read the deploy history, put the server back on the previous tag, check the site, and deploy an older tag by name when one step back is not enough.