Environments
Run staging next to production on one server, with the same names, each on its own private network.
An environment is a group of services and datastores with its own private network. Each server has the environment production. Create staging next to it, and test each change there before it goes to production. Staging uses the same names and the same settings as production. A service of staging cannot reach production by name.
ferry environments create stagingCreate an environment
ferry environments create stagingCreated environment 'staging' (env-3f9a0c1b2d4e5f60718a)
Its web services are served at https://<service>.staging.example.com
Create this DNS record where the DNS of example.com is managed, for *.staging.example.com:
TYPE NAME VALUE
A *.staging 203.0.113.10ferry environments lists the environments. ferry environments show staging shows its services, its datastores and its DNS record.
A name has 1 to 40 lowercase letters, digits or -, and starts with a letter. You cannot change the name later.
Add services and datastores
Give --environment when you create a service or a datastore:
ferry db create shop-db --environment staging
ferry create shop --image nginx:alpine --environment staging
ferry up shop --environment stagingWithout --environment, Ferry puts the service or the datastore in production. You cannot move it to another environment later. In the dashboard, the form of a new service and the dialog of a new datastore have the field Environment.
To see the services of one environment, run ferry services --environment staging. ferry db ls --environment staging does the same for datastores.
Use the same names
Each environment can use the names of the other environments. Thus staging can have web, api and db, with the same settings as production. Then the change that you test in staging is the change that goes to production.
ferry create api --image ghcr.io/acme/api:2.4 --environment staging
ferry env set web --environment staging DATABASE_URL='${{datastore.db.connectionString}}'
ferry deploy api --environment stagingA name is unique in its environment. If staging has api, Ferry refuses a second api in staging:
error: name 'api' is already in use in environment 'staging' (conflict)When a name is in more than one environment, tell Ferry which one you mean:
| Where | How | Without it |
|---|---|---|
| CLI | --environment staging on each command that takes a service or a datastore name | production |
| API | ?environment=staging on each route of one service or datastore | production |
| Dashboard | The URL has the name of the environment: /services/api.staging | /services/api is the service of production |
Thus a script that runs ferry deploy api continues to deploy the api of production. A service id works in all environments. When a name is only in another environment, the error tells you which one:
error: service 'shop' not found in environment 'production' (environment 'staging' has one): name its environment, e.g. --environment staging in the CLI or ?environment=staging in the API (not_found)Two services with the same name never share a container, an image, a volume or a URL. Outside production, a Docker name has the name of the environment after the name of the service or datastore. For example, the datastore db of staging has the container ferry-ds-db.staging and the volume ferry-ds-db.staging-data. Production keeps its Docker names.
A service of production cannot have the name of another environment. An environment cannot have the name of a service of production. The DNS record of the environment hides the URL of that service. For the same reason, an environment cannot have the first part of the address of the dashboard, for example ferry for ferry.example.com.
The URL of a service
A web service or a static site outside production gets the name of its environment in its URL:
| Environment | Service | URL |
|---|---|---|
production | web | https://web.example.com |
staging | web | https://web.staging.example.com |
Production keeps the URLs that it had before environments. Custom domains work in each environment.
Each environment needs one DNS record for each domain of the server: *.staging.example.com, with the address of the server. ferry environments create and the dashboard show the record. A local domain needs no record: http://shop.staging.localhost:8080 works immediately.
What a service reaches
Each container joins the network of its environment only. A job run joins the network of its service.
| From a container of staging, to | Result |
|---|---|
| A service or a datastore of staging, by name | It connects. http://api:8080 is the api of staging. |
| A service or a datastore of production, by name | The name does not resolve. |
| A container of production, by its IP address | No connection, except to a port that Ferry publishes (see below). |
| The public URL of a production service | It connects, through the proxy. |
A reference finds the datastores and the services of the same environment only. When web has DATABASE_URL=${{datastore.db.connectionString}} in both environments, each web gets the db of its environment. If a variable of a staging service refers to a datastore that is only in production, the deploy fails before the build:
cannot resolve environment variables: env var DATABASE_URL: datastore 'db' is not in environment 'staging' (it is in 'production'): a service only reaches the datastores and services of its own environmentferry env set gives a warning for this variable before it saves.
Env groups belong to the whole server, and you can link one group to services of different environments. A reference in a group finds the datastores and the services of the environment of each service. An env group shows a service of another environment as api.staging.
Blueprints apply to production only. A blueprint does not change the services of staging that have the same names. See the blueprint reference.
Delete an environment
ferry environments rm stagingFerry refuses while the environment has services or datastores. Delete them first, for example with ferry delete api --environment staging:
error: environment 'staging' still holds service 'shop', datastore 'shop-db': delete them first (conflict)Ferry then removes the network of the environment. You cannot delete production.
Ferry publishes the port of each datastore and of each service that listens on 127.0.0.1 of the server. The proxy and the health checks use these ports. From Docker Engine 28, a container on another network can connect to a published port at the IP address of its container. Thus a container of staging that knows the IP address of the production datastore, and its password, can connect to it. The names stay isolated. To close this gap, add rules to the DOCKER-USER chain of the firewall of the server. See Security.
All the services and datastores go to production. They keep their network, their Docker names and their URLs. No container restarts.
A second ferryd with its own name prefix also gives a staging setup, with its own account and its own data directory. See Multiple servers.