Deploying listmonk app using secrets or env

Edit: for suggestions in deploying listmonk, skip to the code at the end of this post

I’m quite inexperienced with Docker and new to Fly.io. I’d be grateful for help getting my app deployed.

I’m trying to adapt a Dockerfile and config script for Railway.app. This Dockerfile successfully deploys listmonk on Railway.

Dockerfile:

FROM listmonk/listmonk:latest
ARG PORT ADMIN_PASSWORD ADMIN_USERNAME PGDATABASE PGHOST PGPASSWORD PGPORT PGUSER
COPY config.sh ./config.sh
RUN chmod +x ./config.sh && ./config.sh
RUN ./listmonk --idempotent --yes --upgrade || ./listmonk --install --yes --upgrade

config.sh:

echo "
[app]
address = \"0.0.0.0:${PORT}\"
admin_username = \"${ADMIN_USERNAME}\"
admin_password = \"${ADMIN_PASSWORD}\"
# Database.
[db]
host = \"${PGHOST}\"
port = ${PGPORT}
user = \"${PGUSER}\"
password = \"${PGPASSWORD}\"
database = \"${PGDATABASE}\"
ssl_mode = \"disable\"
max_open = 25
max_idle = 25
max_lifetime = \"300s\"
" > config.toml

When I attempt to launch, I get a panic at the final RUN command with the following relevant lines:

=> ERROR [5/5] RUN ./listmonk --idempotent --yes --upgrade || ./listmonk  0.4s
------
 > [5/5] RUN ./listmonk --idempotent --yes --upgrade || ./listmonk --install --yes --upgrade:
#8 0.365 2022/10/06 16:39:15 main.go:95: v2.2.0 (bbbf28c 2022-07-30T18:18:24Z)
#8 0.365 2022/10/06 16:39:15 init.go:128: reading config: config.toml
#8 0.368 panic: unhandled token: "user" [recovered]
#8 0.368 	panic: interface conversion: interface {} is *errors.errorString, not string

What I’ve done/tried after spending hours searching for answers here and elsewhere:

I’ve added all the secrets with flyctl secrets set PGUSER=someuser PGPASSWORD=blablabla ...

I’ve tried adding one or both of the following to the Dockerfile:

ENV PORT=${PORT} ADMINPORT=${ADMINPORT} ADMIN_PASSWORD=${ADMIN_PASSWORD} ADMIN_USERNAME=${ADMIN_USERNAME} POSTGRES_DATABASE=${POSTGRES_DATABASE} POSTGRES_HOST=${POSTGRES_HOST} POSTGRES_PASSWORD=${POSTGRES_PASSWORD} POSTGRES_PORT=${POSTGRES_PORT} POSTGRES_USER=${POSTGRES_USER} 
RUN --mount=type=secret,id=PORT --mount=type=secret,id=ADMIN_PASSWORD --mount=type=secret,id=ADMIN_USERNAME --mount=type=secret,id=POSTGRES_DATABASE --mount=type=secret,id=POSTGRES_HOST --mount=type=secret,id=POSTGRES_PASSWORD --mount=type=secret,id=POSTGRES_PORT --mount=type=secret,id=POSTGRES_USER 

and I’ve tried running docker deploy as well as:

flyctl deploy --build-arg PORT=$PORT --build-arg ADMINPORT=$ADMINPORT --build-arg ADMIN_PASSWORD=$ADMIN_PASSWORD --build-arg ADMIN_USERNAME=$ADMIN_USERNAME --build-arg POSTGRES_DATABASE=$POSTGRES_DATABASE --build-arg POSTGRES_HOST=$POSTGRES_HOST --build-arg POSTGRES_PASSWORD=$POSTGRES_PASSWORD --build-arg POSTGRES_PORT=$POSTGRES_PORT --build-arg POSTGRES_USER=$POSTGRES_USER

Still, I always get the same error.

If I remove the line referencing user in the Dockerfile, the error that mentions user changes to the next item, password.

Any help would be greatly appreciated!

This looks like it is setting up the database. Using release_command, instead of calling RUN when building the docker image, will ensure this step runs once each time your app is deployed. The release_command will have access to the environment secrets. App Configuration (fly.toml) · Fly Docs

@tvdfly Thank you for this. After I remove the RUN line and put

[deploy]
  release_command = "./listmonk --idempotent --yes --upgrade || ./listmonk --install --yes --upgrade"

in the fly.toml, I do see the step run, but the same error shows up in terminal and the deployment is aborted.

Just to follow up for anyone who happens upon this, now that I think understand things better. If you just want to set up listmonk, skip to the code further down…

App secrets can’t be used at build time, so using them to pass args into deploy is either impossible or not particularly trivial

It is potentially more secure to pass build secrets than to pass build arguments. But build secrets only seem to be able to be used to create a config.toml (via echo or printf in the Docker container, so their values would be there in plain text if anyone got a copy of the container.

Instead, if I use --build-arg and pass environment variables directly into the listmonk app in the Docker container, it seems like those values get stashed only in the database, and don’t stick around in the Docker file. If anyone reads this, please correct me if I’m wrong about that. (On the other hand, using env variables to build a config.toml—as in the original example—suffers from leaving those values stashed in a plain text in the Docker container.)

So here’s how I’m deploying listmonk in Fly.io:

Dockerfile:

FROM listmonk/listmonk:latest
ARG PORT ADMIN_USERNAME ADMIN_PASSWORD POSTGRES_HOST POSTGRES_PORT POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DATABASE
ENV LISTMONK_APP__ADDRESS="0.0.0.0:${PORT}" \
  LISTMONK_APP__ADMIN_USERNAME="${ADMIN_USERNAME}" \
  LISTMONK_APP__ADMIN_PASSWORD="${ADMIN_PASSWORD}" \
  LISTMONK_DB__HOST="${POSTGRES_HOST}" \
  LISTMONK_DB__PORT=${POSTGRES_PORT} \
  LISTMONK_DB__USER="${POSTGRES_USER}" \
  LISTMONK_DB__PASSWORD="${POSTGRES_PASSWORD}" \
  LISTMONK_DB__DATABASE="${POSTGRES_DATABASE}" \
  LISTMONK_DB__SSL_MODE="disable" \
  LISTMONK_DB__MAX_OPEN=3 \
  LISTMONK_DB__MAX_IDLE=1
RUN ./listmonk --config="" --idempotent --yes --upgrade || ./listmonk --config="" --install --yes --upgrade
EXPOSE 9000

deploy command:

flyctl deploy \
  --build-arg PORT="9000" \
  --build-arg ADMIN_USERNAME="<some-username>" \
  --build-arg ADMIN_PASSWORD="<some-password>" \
  --build-arg POSTGRES_HOST="<whatever-connected-postgres-app.internal>" \
  --build-arg POSTGRES_PORT=<port> \
  --build-arg POSTGRES_USER="postgres" \
  --build-arg POSTGRES_PASSWORD="<whatever-postgres-password-is>" \
  --build-arg POSTGRES_DATABASE="postgres_database_name" \
  --build-arg POSTGRES_SSL_MODE="disable" \
  --build-arg POSTGRES_MAX_OPEN=3 \
  --build-arg POSTGRES_MAX_IDLE=1