Ok this is 2024 and after quietly cursing the maintainers of the postgres docker for a couple of hours for making my life hell by stripping me of my root privileges in favor of a 'slightly more secure' yet 'overly complex' postgres user, here is my solution:
In my scenario, i wanted to run certbot to renew my certs every day or so.
Grab a copy of the docker-entrypoint.sh in /usr/local/bin/docker-entrypoint.sh inside the docker image
i.e. docker cp <container>:/usr/local/bin/docker-entrypoint.sh .
You will have to modify this file by adding one line to it and copying back into the image. The line to add will be shown shortly.
Update the docker file like this:
FROM postgres:latest
#do your installs etc (i'm installing certbot and cron here)
# ---below is important---
USER root
COPY ./crontab /etc/cron.d/certbot-renew
RUN chmod 0644 /etc/cron.d/certbot-renew
RUN crontab /etc/cron.d/certbot-renew
USER postgres
COPY ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
clarification : drop into root mode, copy your crontab file into the container, set its permissions and register it with cron, then drop back into postgres user.
The crontab file looks like this:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
0 5 * * * certbot renew -n >> /var/log/cron.log 2>&1
Notice that i'm sending the output to /var/log/cron.log, and not /proc/1/fd/1 - I explain why at the bottom of this answer if you are interested.
Now, start the container with docker compose however include in the docker-compose.yml file the user:0:0 option like so:
services:
postgres:
container_name: ${CONTAINER_NAME}
image: ${IMAGE_NAME}
build:
dockerfile: Dockerfile
context: .
restart: unless-stopped
user: 0:0
Here's the trick - by executing the docker compose with user root it will step into the if statement defined in 'docker-entrypoint.sh' roughly line 314 which demotes you to postgres user- this section here: modify the docker-entrypoint.sh like so:
_main() {
...
if [ "$(id -u)" = '0' ]; then
# then restart script as postgres user
# !!! -HERE IS YOUR ONLY CHANCE BEFORE LOSING ROOT PRIVILEGES
# insert this line below:
service cron start
# now you lose your root privileges
exec gosu postgres "$BASH_SOURCE" "$@"
fi
So the logic is as follows - you start the container as root, the entrypoint starts cron just before dropping you to postgres, and the container starts as normal.
You will notice that I don't log the output (in the crontab) to /proc/1/fd/1 - because yet again - the paranoid maintainers even don't allow root to publish to /proc/1/fd/1 - so to verify your cronjob is running, tail the /var/log/cron.log inside the container - yet another terribly annoying thing so you can't actually see what cron is doing via docker compose logs, You need to log into the container as root. Attempting to log the output of your job to /proc/1/fd/1 will result in 'permission denied' error and it will appear as if cron is not working, as, indeed, the job will fail with error and no output.