Changing Database Credentials on Heroku #6277

This discussion has been migrated from our Github Discussion #6277


finkvi217d ago

I tried to deploy strapi on heroku following by this guide https://strapi.io/documentation/3.0.0-beta.x/deployment/heroku.html for Postgres SQL

Step 7. Heroku Database set-up, block 3. Set environment variables describes manual setting database environments vars

But in Heroku documentation Connecting to Heroku Postgres Databases from Outside of Heroku | Heroku Dev Center I see that Heroku rotates credentials periodically and updates applications where this database is attached.

So, only one complex variable DATABASE_URL is update when Heroku changes credentials. Heroku recommends to fetch the database URL config var from the corresponding Heroku app when your application starts.

I think, I need to parse DATABASE_URL from Heroku and copy to env for strapi. How I can do that?

I tried to start strapi with env from Heroku in package.json:

  "scripts": {
    "develop": "env DATABASE_URL=$(heroku config:get DATABASE_URL -a back-end) strapi develop",
    "start": "env DATABASE_URL=$(heroku config:get DATABASE_URL -a back-end) strapi start",
    "build": "strapi build",
    "strapi": "strapi"
  },

And add parsing code to bootstrap.js:

var parseHerokuUrl = require("parse-database-url")(process.env["DATABASE_URL"]);
console.log(parseHerokuUrl);

process.env["DATABASE_HOST"] = parseHerokuUrl.host;
process.env["DATABASE_PORT"] = parseHerokuUrl.port;
process.env["DATABASE_NAME"] = parseHerokuUrl.name;
process.env["DATABASE_USERNAME"] = parseHerokuUrl.user;
process.env["DATABASE_PASSWORD"] = parseHerokuUrl.password;

console.log(process.env.DATABASE_HOST);

It doesn’t work because config json files load earlier than execute bootstrap.js. I don’t want to create sh file or save DATABASE_URL in .env

Any ideas?

Responses to the discussion on Github


finkvi217d ago

Author

And more experiments with bootstrap.js, see this issue #3558
I can’t understand why code works so:
bootstrap.js:

'use strict';

/**
 * An asynchronous bootstrap function that runs before
 * your application gets started.
 *
 * This gives you an opportunity to set up your data model,
 * run jobs, or perform some special logic.
 *
 * See more details here: https://strapi.io/documentation/3.0.0-beta.x/concepts/configurations.html#bootstrap
 */

// Load environment variables
// Pending this PR: https://github.com/strapi/strapi/pull/3485
require('dotenv').config({ path: require('find-config')('.env') });

console.log ("Call Bootstrap.js OUTSIDE module.exports");

module.exports = () => {    
    return console.log ("Call Bootstrap.js INSIDE module.exports");
};

Console output

~BackEnd $ yarn develop
yarn run v1.22.4
$ strapi develop
Call Bootstrap.js OUTSIDE module.exports
Call Bootstrap.js OUTSIDE module.exports
Call Bootstrap.js OUTSIDE module.exports
Call Bootstrap.js INSIDE module.exports

Why OUTSIDE calls 3 times?


finkvi217d ago

Author

Not interesting solution, but works

Create server.js, set env and then run strapi

const strapi = require('strapi');
const parseHerokuUrl = require("parse-database-url")(process.env["DATABASE_URL"]);

console.log("Parsing Heroku url");
process.env["DATABASE_HOST"] = parseHerokuUrl.host;
process.env["DATABASE_PORT"] = parseHerokuUrl.port;
process.env["DATABASE_NAME"] = parseHerokuUrl.database;
process.env["DATABASE_USERNAME"] = parseHerokuUrl.user;
process.env["DATABASE_PASSWORD"] = parseHerokuUrl.password;
console.log("Starting strapi");

strapi().start();

Change package.json scripts:

  "scripts": {
    "dev": "env DATABASE_URL=$(heroku config:get DATABASE_URL -a back-end) NODE_ENV='development' node ./server.js",
    "develop": "strapi develop",
    "start": "env NODE_ENV='production' node ./server.js",
    "build": "strapi build",
    "strapi": "strapi"
  },

May be add note to strapi documentation?


finkvi217d ago

Author

Hi, anybody here?)

My previous solution was a mistake, although Environment shown as development, content type builder doesn’t work
┌────────────────────┬──────────────────────────────────────────────────┐
│ Time │ Tue May 19 2020 21:09:43 GMT+0300 (Moscow Stand… │
│ Launched in │ 9942 ms │
│ Environment │ development │
│ Process PID │ 30781 │
│ Version │ 3.0.0-beta.20.3 (node v12.16.2) │
└────────────────────┴──────────────────────────────────────────────────┘
There are no differences with NODE_ENV=production

strapi().start() doesn’t the same strapi start.

I am confused, so simple task set env and can’t find solution. Help, pls.


derrickmehaffy217d ago

Collaborator

You can’t use the content-type builder on Heroku period: Troubleshooting - Strapi Developer Documentation

Because model configs are stored in files they would just be reset


finkvi217d ago

Author

Yes, I understand, but it’s another the question


finkvi217d ago

Author

My final solution is awful, but I can’t invent something else, yep, child_process

package.json:

  "scripts": {
    "dev": "node ./server.js develop",
    "develop": "node ./server.js develop",
    "start": "node ./server.js start",
    "build": "node ./server.js build",
    "strapi": "node ./server.js"
  },

server.js:

const parseDatabaseUrl = require("parse-database-url");
const child_process = require("child_process");

if (!process.env["DATABASE_URL"]) {
    try {
        process.env["DATABASE_URL"] = child_process.execSync('heroku config:get DATABASE_URL -a back-end-4-try-sql');
    } catch (err) {
        console.log("Error to get Heroku parameters for database");
        process.exit(1);
    }
}

console.log("Parsing Heroku url");
const parseHerokuUrl = parseDatabaseUrl(process.env["DATABASE_URL"]);
process.env["DATABASE_HOST"] = parseHerokuUrl.host;
process.env["DATABASE_PORT"] = parseHerokuUrl.port;
process.env["DATABASE_NAME"] = parseHerokuUrl.database;
process.env["DATABASE_USERNAME"] = parseHerokuUrl.user;
process.env["DATABASE_PASSWORD"] = parseHerokuUrl.password;

console.log("Starting strapi command");
try {
    child_process.execSync('strapi ' + process.argv[2], {
      stdio: 'inherit',
    });
  } catch (err) {
    process.exit(1);
}