In this final installment of our series, we transition from a development environment to a production-ready setup by deploying our NestJS application integrated with self-hosted Ory services to the Ory Network. This process showcases the interoperability of Ory Network with our existing self-hosted setup and highlights how Ory Network can simplify deployment processes and reduce operational overhead.
Deployment Process Overview
Deploying to Ory Network involves several critical steps to ensure your deployment process is secure and scalable. Here’s a high-level overview of the process, depicted as a flowchart:
Detailed Steps for Deployment
Account Registration
We start by registering an account with Ory Network. This account will manage our projects and workspaces within the Ory Network and add collaborators to our projects.
Create Workspace
Create a new workspace within the Ory Network. This workspace will help you organize your projects effectively and is the first step towards deploying your application. Ory Network allows you to create multiple workspaces, each serving as an isolated environment for different projects and billing.
Create project
Within your new workspace, register a new project named CatFostering. In practice, ensure your project is scoped appropriately for the environment (development, staging, production).
Install Ory CLI
Install the Ory CLI to interact with the Ory Network from your command line, which is essential for subsequent configuration steps.
When you have the CLI installed, you can run the following command and follow the prompts to authenticate:
ory auth
Build tools to configure the Ory Network project
Before updating the Ory Network project, we need to generate the necessary configuration file from our existing configuration templates for Kratos
and Keto
and translate them into a format compatible with Ory Network. There are a few things to note:
- File references pointing to local files (
file://
protocol) won't work on Ory Network. The file content should be inlined after being encoded to base64 or uploaded to cloud storage and linked to the configuration. - URLs pointing to local services (127.0.0.1, localhost, 192.168.x.y) will result in a schema validation error. They must resolve to a public address.
- The following configuration keys are ignored by Ory Network:
dsn
log
server
secrets
session.cookie.domain
session.cookie.name
session.cookie.path
cookies.path
cookies.domain
The Ory Network configuration file should be in JSON or YAML format and follow the schema defined by Ory Network:
type OryNetworkConfig = {
name: string;
services: {
identity: {
config: Record<string, unknown>;
};
permission: {
config: Record<string, unknown>;
};
};
};
Note:
Underidentity
andpermission
, theconfig
object should contain the configuration forKratos
andKeto
, respectively.
We will create a function - generateOryNetworkConfig
- relying on the previously built tools to generate the configuration file for Ory Network.
// from https://github.com/getlarge/cat-fostering/blob/main/tools/ory/helpers.ts
// ...
export function generateOryNetworkConfig(envFile: string) {
const kratosConfigFilePath = join(
ORY_NETWORK_DIRECTORY,
'identity.yaml'
) as ConfigFilepath;
const ketoConfigFilePath = join(
ORY_NETWORK_DIRECTORY,
'permission.yaml'
) as ConfigFilepath;
generateOryKetoConfig(envFile, ketoConfigFilePath);
generateOryKratosConfig(envFile, kratosConfigFilePath);
const oryConfig: OryNetworkConfig = {
name: 'CatFostering',
services: {
identity: {
config: getOryConfig(kratosConfigFilePath),
},
permission: {
config: getOryConfig(ketoConfigFilePath),
},
},
};
return oryConfig;
}
Expose the application to the internet
Our NestJS application receives webhooks from Ory Hydra, which is running locally. With Ory Network running on the cloud, the application must be accessible via a public URL. To expose your local development environment to the internet, utilize a tunnel service such as Tailscale Funnel, ngrok, webhook.site, or others. This step is crucial for receiving webhooks from Ory Network.
I am a fan of Tailscale products due to their simplicity and ease of use, and Funnel
makes no exception. To expose your local application, you would run the following command:
tailscale funnel --set-path cat-fostering 3000
Note:
This command exposes your local application running on port 3000 to the internet under the FQDN generated by Tailscale Magic DNS with the path/cat-fostering
(e.g.,https://<machine_name>.<domain_name>.ts.net/cat-fostering
).
Update Ory Network Project
To generate the Ory configuration, we first need to store the variables to substitute in the .env.development
file using the values from:
- The Ory Network console under the
Project Settings
(e.g.https://console.ory.sh/projects/<project-id>/settings
) - The tunnel service for the public URL
- The encoded files to inline in the configuration
The following variables are required:
# env.development
kratos_cookie_domain="<ory_project_domain>"
kratos_identity_schemas_default="base64://<base64_encoded_content>"
kratos_selfservice_allowed_return_urls="https://<ory_project_domain>, https://<ory_project_domain>/ui/error, https://<ory_project_domain>/ui/login, https://<ory_project_domain>/ui/recovery, https://<ory_project_domain>/ui/settings, https://<ory_project_domain>/ui/registration, https://<ory_project_domain>/ui/verification, /ui/logout, /ui/consent, /ui/welcome, /ui/sessions"
kratos_selfservice_default_browser_return_url="/ui/welcome"
kratos_selfservice_flows_errors_ui_url="https://<ory_project_domain>/ui/error"
kratos_selfservice_flows_login_after_hook_config_url="<tunnel_url>/api/users/on-sign-in"
kratos_selfservice_flows_login_after_hook_config_auth_config_value="<your-own-internal-api-key>"
kratos_selfservice_flows_login_after_hook_config_body="base64://<base64_encoded_content>"
kratos_selfservice_flows_login_after_hook_config_can_interrupt="true"
kratos_selfservice_flows_login_after_hook_config_response_ignore="false"
kratos_selfservice_flows_login_after_hook_config_response_parse="false"
kratos_selfservice_flows_login_ui_url="https://<ory_project_domain>/ui/login"
kratos_selfservice_flows_recovery_ui_url="https://<ory_project_domain>/ui/recovery"
kratos_selfservice_flows_registration_after_hook_config_url="<tunnel_url>/api/users/on-sign-up"
kratos_selfservice_flows_registration_after_hook_config_auth_config_value="<your-own-internal-api-key>"
kratos_selfservice_flows_registration_after_hook_config_body="base64://<base64_encoded_content>"
kratos_selfservice_flows_registration_after_hook_config_can_interrupt="true"
kratos_selfservice_flows_registration_after_hook_config_response_ignore="false"
kratos_selfservice_flows_registration_after_hook_config_response_parse="true"
kratos_selfservice_flows_registration_ui_url="https://<ory_project_domain>/ui/registration"
kratos_selfservice_flows_settings_ui_url="https://<ory_project_domain>/ui/settings"
kratos_selfservice_flows_verification_ui_url="https://<ory_project_domain>/ui/verification"
kratos_selfservice_methods_passkey_config_rp_id="<ory_project_domain>"
kratos_selfservice_methods_passkey_config_rp_origins="https://<ory_project_domain>"
kratos_selfservice_methods_passkey_enabled="false"
kratos_selfservice_methods_webauthn_config_rp_id="<ory_project_domain>"
kratos_selfservice_methods_webauthn_config_rp_origins="https://<ory_project_domain>"
kratos_selfservice_methods_webauthn_enabled="false"
kratos_secrets_cookie="cookie_secret_not_good_not_secure"
kratos_secrets_cipher="32-LONG-SECRET-NOT-SECURE-AT-ALL"
kratos_serve_admin_base_url="https://<ory_project_domain>/"
kratos_serve_public_base_url="https://<ory_project_domain>/"
kratos_serve_public_cors_enabled="false"
kratos_session_cookie_domain="<ory_project_domain>"
kratos_session_cookie_name="ory_session_<ory_project_slug>"
keto_namespaces_location="base64://<base64_encoded_content>"
Tip:
To encode a file to base64, you can use the following command:base64 -i <file_path>
And copy the output to the configuration file, prepending
base64://
. (e.g.base64://<base64_encoded_content>
)
The Ory CLI contains a command to update the project configuration with the generated configuration file. To simplify its usage, we will create a helper function - updateOryNetworkConfig
- that generates the configuration file and updates the project configuration.
// from https://github.com/getlarge/cat-fostering/blob/main/tools/ory/helpers.ts
// ...
export function updateOryNetworkConfig(projectId: string, envFile: string) {
const oryConfig = generateOryNetworkConfig(envFile);
console.log(inspect(oryConfig, { depth: null }));
const oryNetworkConfigPath = join(ORY_NETWORK_DIRECTORY, 'config.json');
writeFileSync(oryNetworkConfigPath, JSON.stringify(oryConfig));
execSync(
`ory update project ${projectId} --format json --file ${oryNetworkConfigPath}`,
{
stdio: 'inherit',
}
);
}
Note:
TheupdateOryNetworkConfig
function will be imported and used ingenerate-config.ts
, which will be referenced in thepackage.json
scripts.
Run the following command to generate the Ory configuration and update the project configuration on the Ory Network:
npm run ory:update:network -e .env.development --project-id <project_id>
The CLI output should indicate the successful update; we can check the Ory Console for the updated configuration.
Note:
Above is an example of the permissions configuration in the Ory Console. The configuration should reflect the settings in the encoded value of infra/ory-keto/namespaces.ts.
This tool can also be a great addition to a CI/CD pipeline by automating the configuration update process for multiple Ory Network tenants.
Here is a simplified example using GitHub Actions to trigger the deployment process:
name: Update Ory Network Config
on:
push:
branches:
- main
env:
ORY_PROJECT_ID: ${{ vars.ORY_PROJECT_ID }}
ORY_PROJECT_DOMAIN: ${{ vars.ORY_PROJECT_DOMAIN }}
ORY_PROJECT_URL: https://${{ vars.ORY_PROJECT_DOMAIN }}
APPLICATION_URL: ${{ vars.APPLICATION_URL }}
jobs:
run-ory-cli:
runs-on: ubuntu-latest
steps:
# checkout the repository, setup node, ...
- name: encode Files to Base64
id: ory-files
shell: bash
run: |
echo "KRATOS_IDENTITY_SCHEMAS_DEFAULT=$(base64 -i infra/ory-kratos/identity-schemas.json)" >> $GITHUB_OUTPUT
echo "KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY=$(base64 -i infra/ory-kratos/after-webhook.jsonnet)" >> $GITHUB_OUTPUT
echo "KETO_NAMESPACES_LOCATION=$(base64 -i infra/ory-kratos/identity-schemas.json)" >> $GITHUB_OUTPUT
- name: generate Ory Network Config
env:
kratos_identity_schemas_default: base64://${{ steps.ory-files.outputs.KRATOS_IDENTITY_SCHEMAS_DEFAULT }}
kratos_selfservice_allowed_return_urls: ${{ env.ORY_PROJECT_URL }}, ${{ env.ORY_PROJECT_URL }}/ui/error, ${{ env.ORY_PROJECT_URL }}/ui/login, ${{ env.ORY_PROJECT_URL }}/ui/recovery, ${{ env.ORY_PROJECT_URL }}/ui/settings, ${{ env.ORY_PROJECT_URL }}/ui/registration, ${{ env.ORY_PROJECT_URL }}/ui/verification, /ui/logout, /ui/consent, /ui/welcome, /ui/sessions
kratos_selfservice_default_browser_return_url: /ui/welcome
kratos_selfservice_flows_errors_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/error
kratos_selfservice_flows_login_after_hook_config_url: ${{ env.APPLICATION_URL }}/api/users/on-sign-in
kratos_selfservice_flows_login_after_hook_config_auth_config_value: ${{ secrets.ORY_ACTION_API_KEY }}
kratos_selfservice_flows_login_after_hook_config_body: base64://${{ steps.ory-files.outputs.KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY }}
kratos_selfservice_flows_login_after_hook_config_can_interrupt: true
kratos_selfservice_flows_login_after_hook_config_response_ignore: false
kratos_selfservice_flows_login_after_hook_config_response_parse: false
kratos_selfservice_flows_login_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/login
kratos_selfservice_flows_recovery_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/recovery
kratos_selfservice_flows_registration_after_hook_config_url: ${{ env.APPLICATION_URL }}/api/users/on-sign-up
kratos_selfservice_flows_registration_after_hook_config_auth_config_value: ${{ secrets.ORY_ACTION_API_KEY }}
kratos_selfservice_flows_registration_after_hook_config_can_interrupt: true
kratos_selfservice_flows_registration_after_hook_config_response_ignore: false
kratos_selfservice_flows_registration_after_hook_config_response_parse: true
kratos_selfservice_flows_registration_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/registration
kratos_selfservice_flows_settings_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/settings
kratos_selfservice_flows_verification_ui_url: ${{ env.ORY_PROJECT_URL }}/ui/verification
kratos_selfservice_methods_passkey_config_rp_id: ${{ env.ORY_PROJECT_DOMAIN }}
kratos_selfservice_methods_passkey_config_rp_origins: ${{ env.ORY_PROJECT_URL }}
kratos_selfservice_methods_passkey_enabled: false
kratos_selfservice_methods_webauthn_config_rp_id: ${{ env.ORY_PROJECT_DOMAIN }}}
kratos_selfservice_methods_webauthn_config_rp_origins: ${{ env.ORY_PROJECT_URL }}
kratos_selfservice_methods_webauthn_enabled: false
kratos_serve_admin_base_url: ${{ env.ORY_PROJECT_URL }}/
kratos_serve_public_base_url: ${{ env.ORY_PROJECT_URL }}/
kratos_serve_public_cors_enabled: false
keto_namespaces_location: base64://${{ steps.ory-files.outputs.KRATOS_SELF_SERVICE_FLOWS_LOGIN_AFTER_HOOK_CONFIG_BODY }}
run: npm run ory:generate:network
- name: Update Ory Network Config
uses: lomsa-dev/ory-cli-action@v1.0.8 # use the latest version
with:
username: ${{ secrets.ORY_USERNAME }}
password: ${{ secrets.ORY_PASSWORD }}
commands: |
update project {{ env.ORY_PROJECT_ID }} --file infra/ory-network/config.json
Create API key
Generate an API key within the Ory Network console under the developers
(https://console.ory.sh/projects/<project-id>/developers
) tab. This key will authenticate your application's requests to Ory services.
Configure CatFostering Application
Update your application's configuration to use the API keys and endpoints provided by Ory Network, ensuring all interactions are routed correctly.
# apps/cat-fostering/.env
POSTGRES_URL="postgresql://dbuser:secret@localhost:5432/appdb"
ORY_ACTION_API_KEY="<your-own-internal-api-key>"
ORY_KETO_ADMIN_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KETO_PUBLIC_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_ADMIN_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_PUBLIC_URL="https://<Ory_project_slug>.projects.oryapis.com"
ORY_KRATOS_API_KEY="ory_pat_xxxxxxxx"
ORY_KETO_API_KEY="ory_pat_xxxxxxxx"
You can now start your application and test the integration with Ory Network.
Ory Proxy and Ory Tunnel
We won't need them for our case since the CatFostering
doesn't have a front-end web application. However, it is worth knowing that Ory Proxy and Ory Tunnel are tools included in the Ory CLI to assist with local development. They are used to expose Ory APIs under the same top-level domain as your application to avoid CORS issues.
Conclusion
In this article, we explored the deployment process to Ory Network to transition our NestJS application from a development environment to a production-ready setup. Following the detailed steps outlined above, you can move your local Ory setup to the cloud in no time. The interoperability of Ory Network with our existing self-hosted setup and its ease of deployment make it a compelling choice for developers looking to streamline their deployment processes and reduce operational overhead.
To go further, you can explore additional features of Ory Network, such as CORS configuration and custom domains, to enhance your application's security and user experience.
To complete your knowledge on this topic, I suggest reading the following article written by the Ory team: Ory Network or self-hosted?
I hope this series has provided valuable insights to secure your NestJS applications with Ory and streamline your development workflows.
Top comments (0)