Docker & CommandBox - custom startup

If one would like to build a custom Docker image starting from one of the official CommandBox Docker images, and supply a different CMD command in the Dockerfile, how would that be accomplished? Specifically, I’m not sure what command I need to execute within the init.sh script shown below to startup the CFML server (the same as if I had no custom startup) after I’ve done some other tasks first. Thanks so much! -Wes

Dockerfile

FROM ortussolutions/commandbox:lucee5-alpine

# Copy application files and custom startup script to root
COPY app-files/ ${APP_DIR}/
COPY init.sh /root/init.sh

# Tell containers which script to run at startup
CMD ["/root/init.sh"]

init.sh

#!/bin/bash

# Do cool stuff here

# Start-up the CFML server 
# /usr/bin/startup.sh  ### <-- WHAT COMMAND TO TYPE HERE? 

Hi Wes! Can you help us my explaining what you’re wanting to customize? I know some people love to override their entry points, but I always consider it a bit of a code smell if you’re trying to work around what the image already does. Ideally you’ve taken care of all the shuffling of files around in your docker build and at deploy time there should be nothing left to do but start the server! There may be a better way if we know what you need to happen on server boot.

Oh it’s potentially very smelly indeed, that’s why I kept it vague :grin:

At the moment, within the init.sh script I’m injecting Docker secrets into some files with sed commands. I’m aware there is some functionality provided where one can create environment variables based on Docker Secrets then use them in within the config files in some way. This is my next route to try, but so far I’ve just been following some convention I’ve been using in other non-CFML apps (R Shiny apps). So I might not need it this exact time if I can get the env stuff going, but I was curious, and potentially in need, of learning how it could be done. Thanks for your reply! -Wes

If I attempt the method of the _FILE suffix convention then what do I need to put in Coldbox.cfc or Application.cfc to get those values?

Coldbox.cfc

reinitPassword = "",  // <-- What to put here?

It looks like I could do this: reinitPassword = getSystemSetting("REINIT_PASSWORD") but that appears to require ColdBox v.5 or greater? I’ve got a legacy app with Coldbox v.4, unfortunately.

So, back to my original question, how does one use a custom start-up script as part of a custom Docker image build on top of the official CommandBox Docker images? Thanks! -Wes

@wphampton , am I right in understanding you want to build a custom Docker image to help you load environment variables into your app?

If Yes, then you’re wasting your time - baking env vars into your Docker image won’t actually help you load environment variables into your app. :slight_smile: You’ll still need a line like getSystemSetting("REINIT_PASSWORD") or similar to read the environment variables into ColdBox.

Besides that, you don’t need to build a custom image to pass env vars into a container! You can pass a .env file via docker run --env-file or in a docker-compose.yml file.

Take a step back:

  • What exactly are you trying to accomplish?
  • Why do you specifically need Docker secrets instead of simpler configuration variables?
  • Will a .env file combined with commandbox-dotenv module work for your purposes?
  • Did you try Googling “How to read environment variables in CFML”?
1 Like

PS. @wphampton I’m a huge fan of yours, would love a signature some time. :wink:

https://gaither.com/artists/wes-hampton/

1 Like

Haha, it’s really remarkable how much hair he has! I don’t think I’ve ever had that much at any point in life.

So, I appreciate you taking time to write. My situation is this, I’m trying to get a large Coldbox v.4 app off of old ACF servers and onto Docker. Everything is going well and I am at the point of creating Docker secrets like I’ve done for other non-CFML apps.

  • What exactly are you trying to accomplish?
    • Inject Docker secrets into the correct config files at container runtime.
  • Why do you specifically need Docker secrets instead of simpler configuration variables?
    • I’m not sure what more simple mechanisms should be considered, but Docker Secrets is how we store database passwords, sensitive config settings…etc across a Swarm. I am already saving the entire .cfconfig.json file as a Docker Secret, I suppose I could also store the entire ColdBox.cfc and Application.cfc as well rather than try to update certain
  • Will a .env file combined with commandbox-dotenv module work for your purposes?
    • Does that work with Coldbox v.4? I’ve already found that getSystemSetting() does not.
  • Did you try Googling “How to read environment variables in CFML”?
    • Yes, I’ve read this but haven’t tried if that will work within ColdBox.cfc and Application.cfc.

I can’t quite fit the puzzle that is the CommandBox Docker image build scripts together in my head, but it seems like something similar to my first post above should be possible, and has been in my experience with extending other images. Typically there is just one script that can be called that is the default if you weren’t tinkering with a custom entrypoint. I’m just trying to make as few changes as possible to the original codebase of these legacy apps.

Thanks!
Wes

1 Like

Isn’t this doing half of what you need here…

Handle deprecated/changed environment variables

. $BUILD_DIR/util/compat-env.sh

Here is how ColdBox gets system settings etc…

Maybe you can use this in your version, add it as your own cfc… you can use it old school in your application.cfc like this

You could use that function to pull those env variables, that the run already is loading… not sure you need to make your own cmd runner… based on my understanding of what you are wanting to do.

I could be wrong… I often am :slight_smile:

1 Like

Ok, so you’re looking for two distinct things here:

  1. Using Docker Secrets with a CommandBox Docker Image
  2. Accessing Environment Variables from ColdBox

Using Docker Secrets with a CommandBox Docker Image

I don’t actually know “the answer” on this one, as I’ve never used secrets in Docker before. However, I do know that you don’t need to override the CMD or the ENTRYPOINT in your custom image.

You should be able to create a custom image using nothing but this line in your Dockerfile:

FROM ortussolutions/commandbox:adobe2018

You don’t have to “do” anything extra to ensure the server starts up on container run.

Once you have that :point_up: , pulling in the docker secret is really just following the docs, IMHO.

Accessing Environment Variables from ColdBox

Lots of ways to skin the cat here. What Gavin said should work - basically, copy getSystemSetting() from ColdBox’s Util package and paste into your Coldbox.cfc or into a models/Util custom component. Once you have that, you can run getSystemSetting( "MY_ENV_SECRET" ) to get the secret value.

You can also use the commandbox-dotenv module in ANY server started from CommandBox to read a .env file and copy them to Java properties. (Which can then be read using the getSystemSetting() code above…)

I would also take a look at this dotenvsettings module which reads in a .env file and sets them as ColdBox properties. That may very well work on ColdBox 4, though I’m not sure.

Thank you @MichaelBorn and @gpickin The getSystemSetting() seemed like the right way to go, though I also needed to copy getJavaSystem() since I was trying to do this in ColdBox 4. I was still not able to access the Environment Variable this way though, so…

I took some time and was able to make the jump to Coldbox 5.6.2, which has the getSystemSetting() natively included, but I am still not able to get ColdBox to recognize the Environment variable. Here’s what I’m doing:

Docker service creation:

docker service create --secret source=swarm-secret-reinit-pwd,target=reinit-pwd --env REINIT_PASSWORD_FILE=/run/secrets/reinit-pwd <other parameters> my-image-name:1.0

When the container starts up I see this line in the logs, which I’d expect:

Expanding from _FILE suffix

Here’s my Coldbox.cfc

ReinitPassword = getSystemSetting("REINIT_PASSWORD", "abc"),  // <-- This always resolves to "abc" instead of the REINIT_PASSWORD

However when the container is running, the reinit password which is actually used is “abc” instead of the password I’ve actually saved with Docker Secrets. When I SSH into the container I am able to see the password at /run/secrets/reinit-pwd so Docker has definitely made it available, it just seems that it did not get made into an Environment Variable that Coldbox sees, or I’ve done something incorrect along the way.

Thanks for any remaining guidance you can provide.

Wes

Hmm. Maybe you need to check for the env var by SSHing into the container, and running:

env | grep REINIT_PASSWORD

If the env var is in the container but not in the application, then it may be lacking a step to copy it to the application context. :man_shrugging: I am out of my depth on this, unfortunately.

Interesting. I wasn’t sure if this was a Linux-level environment variable which would be created by CommandBox or what exactly it was doing, but it does not get created at the OS-level. When I type the command you gave me, I just see the original variable which I specified when I created the Docker service.

env | grep REINIT_PASSWORD

REINIT_PASSWORD_FILE=/run/secrets/reinit-pwd

If I do this: cat /run/secrets/reinit-pwd then the password I am expecting is printed, so Docker is mounting and providing the secret correctly.

When I add in another environment variable (--env ENV_SECRETS_DEBUG=1) to the Docker service in order to see the debugging information I see that upon container start-up it claims it has expanded the variable to REINIT instead of the expected REINIT_PASSWORD.

Secret file for REINIT_PASSWORD_FILE: /run/secrets/reinit-pwd

Expanding from _FILE suffix

Expanded variable: REINIT=mysecretpassword

However, the env command still doesn’t list REINIT as an environment variable either, at the Linux OS level within the container.

Ok, finally something positive here. Knowing that it expanded the variable to REINIT instead of REINIT_PASSWORD I inserted a line within the onRequestStart() method:

Application.cfc

public boolean function onRequestStart(string targetPage){
    writeOutput(util.getSystemSetting("REINIT", "abc"));
}

And printed to the screen was the actual password I’ve been expecting to see.

1 Like

I opened an issue, I think this _FILE suffix expansion code has a bug and is omitting pieces.

I agree, probably a bug.

Nice debugging work!

1 Like