Productionizing a golang binary
OUTLINE
- Introduction
- Types of systemd service
- Attempt One
- Attempt Two
- Attempt Three
- Attempt Four
- Attempt Five (Docker)
- Conclusion
Introduction
So after developing my golang project, a vanity url mapper for my golang packages, I needed a way to deploy it from my local machine to my server. So this means I would run it as a systemd1 service. For this, I would make the systemd service run it,
(a) either locally on my server filesystem, or
(b) in a docker container.
Types of systemd service
A systemd service is normally defined in a unit file. The syntax and description of the unit file can be found by running man systemd.unit
on any Linux system that supports systemd. So there are conventionally two types of systemd services
(a) System service: This service is enabled for all users that log into the system. To do this, you would normally run this set of commands:
$ sudo cp <SERVICE_NAME>.service /etc/systemd/system/
$ sudo systemctl daemon-rDeploying to run on the filesystemeload
$ sudo systemctl enable <SERVICE_NAME>.service
(b) User service: The service is enabled for only the logged-in user. To do this, you would normally run this set of commands:
$ touch /home/<$USER>/.config/systemd/user/<SERVICE_NAME>.service
$ systemctl --user reload
$ systemctl --user enable --now <SERVICE_NAME>
Enabling the service will then symlink it into the appropriate .wants
subdirectory, and it will run only when that user is logged in. This means that if that user is not logged in, systemd might terminate the service2.
The systemd user instance is started after the first login of a user and killed after the last session of the user is closed. Sometimes it may be useful to start it right after boot, and keep the systemd user instance running after the last session closes, for instance to have some user process running without any open session
This is bad for long-running transcations/services. So to do that, you would run this
$ loginctl enable-linger $USER
This disconnects that logged-in user specific systemd instance from that logged-in user session.
Attempt One
I did initially not know the difference between the types of systemd services. So I ran these steps:
(a) Created a sample service
[Unit]
Description=A custom url mapper for go packages!
After=network-online.target
[Service]
ExecStart=/opt/gocustomurls -conf /home/vagrant/.config/config.json
SyslogIdentifier=gocustomurls
StandardError=journal
Type=exec
[Install]
WantedBy=multi-user.target
(b) Ran these steps
$ sudo cp gocustomurls.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl start gocustomurls.service
$ sudo systemctl status gocustomurls.service
So I got this error
× gocustomurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/etc/systemd/system/gocustomurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf
Active: failed (Result: exit-code) since Fri 2024-07-19 03:31:19 UTC; 54s ago
Duration: 23ms
Process: 3027 ExecStart=/opt/gocustomurls -conf /home/vagrant/.config/config.json (code=exited, status=1/FAILURE)
Main PID: 3027 (code=exited, status=1/FAILURE)
CPU: 6ms
$ http --body "http://localhost:7070/x/touche?go-get=1"
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="scale.dev/x/migrate git https://codeberg.org/Gusted/mCaptcha.git">
<meta name="go-source" content="scale.dev/x/migrate https://codeberg.org/Gusted/mCaptcha.git https://codeberg.org/Gusted/mCaptcha.git/tree/main{/dir} https://codeberg.org/Gusted/mCaptcha.git/blob/main{/dir}/{file}#L{line}">
</head>
</html>
Jul 19 03:31:19 fedoramachine systemd[1]: Starting gocustomurls.service - GocustomUrls. A custom url mapper for go packages!...
Jul 19 03:31:19 fedoramachine systemd[1]: Started gocustomurls.service - GocustomUrls. A custom url mapper for go packages!.
Jul 19 03:31:19 fedoramachine gocustomurls[3027]: 2024/07/19 03:31:19 File: => /home/vagrant/.config/config.json
Jul 19 03:31:19 fedoramachine gocustomurls[3027]: 2024/07/19 03:31:19 Ok: => false
Jul 19 03:31:19 fedoramachine gocustomurls[3027]: 2024/07/19 03:31:19 Warning, generating default config file
Jul 19 03:31:19 fedoramachine gocustomurls[3027]: 2024/07/19 03:31:19 neither $XDG_CONFIG_HOME nor $HOME are defined
Jul 19 03:31:19 fedoramachine systemd[1]: gocustomurls.service: Main process exited, code=exited, status=1/FAILURE
Jul 19 03:31:19 fedoramachine systemd[1]: gocustomurls.service: Failed with result 'exit-code'.
Basically the error is that the $HOME environment variable is empty. This is consistent with some of the properties of system services which are:
-
Their spawned processes inherit NO environment (e.g., in a shell script run by the service, the $HOME environment variable will actually be empty)
-
They run as root by default. As a result, they have root permissions.
Attempt Two
So I tried to fix some of the issues with my first attempt by creating a user service. I went through these steps:
(a) Created a non-root user.
Debian
$ sudo adduser \
--system \ # Non-expiring accounts
--shell /bin/bash \ # the login shell
--comment 'Go Custom Urls Service' \ # removes finger info, making the command non-interactive
--group \ # creates an identically named group as its primary group
--disabled-password --home /home/$USER $USER
Fedora
$ sudo useradd --system --shell /bin/bash --comment 'Go Custom Urls Service' --home-dir /home/gourls -m gourls
(b) Unlocked the account by deleting the password
$ sudo passwd -d gourls // to unlock the account
(c) Fixed the permissions. I used this resource3 and this resource4 to generate the required permissions.
$ sudo chown -R gourls:gourls /home/gourls
$ sudo chmod -R 770 /home/gourls
(d) Modified the servcie unit above to change the invocation of the service to a user throught the use of the User=
and Group=
directives.
[Unit]
Description=GocustomUrls. A custom url mapper for go packages!
After=network-online.target
[Service]
User=gols
Group=gols
ExecStart=/home/gols/gocustomurls -conf /home/gols/.config/gocustomurls/config.json
SyslogIdentifier=gocustomurls
StandardError=journal
Type=simple
WorkingDirectory=/home/gols
Environment=USER=gols HOME=/home/gols
[Install]
WantedBy=multi-user.target
(e) Copied the unit file to the config folder.
$ sudo mkdir -p /home/gols/.config/systemd/user
$ sudo chmod -R 770 /home/gols
$ sudo cp gocustomurls.service /home/gols/.config/systemd/user/
$ sudo chown -R gols:gols /home/gols
(f) Reloaded systemd services
$ su gols
$ systemctl --user daemon-reload
Failed to connect to bus: Permission denied
So after googling this error, I was told to check if my XDG_RUNTIME_DIR
and DBUS_SESSION_BUS_ADDRESS
was set correctly. So checking,
$ id
uid=993(gols) gid=992(gols) groups=992(gols) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ echo $XDG_RUNTIME_DIR
/run/user/1000
$ echo $DBUS_SESSION_BUS_ADDRESS
unix:path=/run/user/1000/bus
As you can see above, the variables XDG_RUNTIME_DIR
and DBUS_SESSION_BUS_ADDRESS
are configured with the incorrect user id. So I tried to export them in the user's ~/.bash_profile
$ cat ~/.bash_profile
...
export XDG_RUNTIME_DIR="/run/user/$UID"
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus
This did not help. So after googling, I came across this resource5 and tried that
$ systemctl -M gols@ --user daemon-reload
Failed to connect to bus: Permission denied
So after further googling, I came across this resource6. From this resource, there are basically two requirements for systemctl --user
command to work which are:
-
The user instance of the systemd daemon must be alive, and
-
systemctl
must be able to find it through certain path variables.
For the first condition, the loginctl enable-linger
command enables that to happen by starting a user instance of systemd even if the user is not logged in. For the second condition, su
or su --login
does not set the required variables. To do that, you would normally ssh into that user's session. This is obviously a non-starter for me. But there is a new command called machinectl
7 that obviates that need. So armed with this information, I was ready for another attempt.
Attempt Three
So machinectl
7 is available from systemd-container
(Debian/Fedora). So I ran these steps based on this resource8 and this resource5:
$ sudo dnf install systemd-container
$ sudo loginctl enable-linger gols
$ sudo machinectl shell --uid=gols
Connected to the local host. Press ^] three times within 1s to exit session.
$ exit
$ sudo systemctl -M gourls@ --user daemon-reload
$ su gopls
$ echo $XDG_RUNTIME_DIR
/run/user/993
$ echo $DBUS_SESSION_BUS_ADDRESS
unix:path=/run/user/993/bus
In order to the command sudo systemctl -M gourls@ --user daemon-reload
to be successful, the variables from the previous attempt must be exported in ~/.bash_profile
. Okay, I then ran a journalctl command to check the status of the failed service
$ journalctl --user -u gocustomurls.service
Hint: You are currently not seeing messages from the system.
Users in groups 'adm', 'systemd-journal', 'wheel' can see all messages.
Pass -q to turn off this notice.
No journal files were opened due to insufficient permissions.
So I added the gourls user to the systemd-journal group
$ sudo usermod -a -G systemd-journal gols
$ sudo systemctl -M gols@ --user list-units | grep "gocustom" # list all the units and find gocustom
So far so good. Restarting the service produces an error
$ sudo systemctl -M gourls@ --user restart gocustomurls.service
$ su gols
$ journalctl --user -u gocustomurls.service
Jul 19 07:19:03 fedoramachine systemd[749]: Started gocustomurls.service - GocustomUrls. A custom url mapper for go packages!.
Jul 19 07:19:03 fedoramachine (stomurls)[3291]: gocustomurls.service: Failed to determine supplementary groups: Operation not permitted
Jul 19 07:19:03 fedoramachine systemd[749]: gocustomurls.service: Main process exited, code=exited, status=216/GROUP
Jul 19 07:19:03 fedoramachine systemd[749]: gocustomurls.service: Failed with result 'exit-code'.
Based on the error above, it seemed that the group was incorrect. This observation was also confirmed after viewing this9. So I removed the User=
, Group=
and Environment=
directoves from the unit file and restarted the service.
[Unit]
Description=GocustomUrls. A custom url mapper for go packages!
After=network-online.target
[Service]
ExecStart=/home/gols/gocustomurls -conf /home/gols/.config/gocustomurls/config.json
SyslogIdentifier=gocustomurls
StandardError=journal
WorkingDirectory=/home/gols
Type=simple
[Install]
WantedBy=multi-user.target
$ sudo cp gocustomurls.service /home/gols/.config/systemd/user/
$ sudo chown -R gols:gols /home/gols
$ sudo machinectl shell --uid=gols
Connected to the local host. Press ^] three times within 1s to exit session.
$ exit
logout
Connection to the local host terminated.
$ sudo systemctl -M gols@ --user daemon-reload
$ sudo systemctl -M gols@ --user restart gocustomurls.service
$ sudo systemctl -M gols@ --user status gocustomurls.service
× gocustomurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/home/gols/.config/systemd/user/gocustomurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/user/service.d
└─10-timeout-abort.conf
Active: failed (Result: exit-code) since Fri 2024-07-19 10:02:31 UTC; 8min ago
Duration: 1ms
Process: 2919 ExecStart=/home/gols/gocustomurls -conf /home/gols/.config/gocustomurls/config.json (code=exited, status=203/EXEC)
Main PID: 2919 (code=exited, status=203/EXEC)
CPU: 1ms
The error was because of a missing routes.json
. The binary that the gocustomurls.service runs, returns an exit code if routes.json
is not found. Adding a rules.json
to the folder that is read by the gocustomurls.service returns a success
$ sudo cp rules.json /home/gols/.config/gocustomcurls/
$ sudo systemctl -M gols@ --user daemon-reload
$ sudo systemctl -M gols@ --user restart gocustomurls.service
$ sudo systemctl -M gols@ --user status gocustomurls.service
● gocustomurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/home/gols/.config/systemd/user/gocustomurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/user/service.d
└─10-timeout-abort.conf
Active: active (running) since Sat 2024-07-20 05:16:31 UTC; 28s ago
Main PID: 3158
Tasks: 6 (limit: 2319)
Memory: 1.7M (peak: 2.0M)
CPU: 4ms
CGroup: /user.slice/user-993.slice/user@993.service/app.slice/gocustomurls.service
└─3158 /home/gols/gocustomurls -conf /home/gols/.config/gocustomurls/config.json
Testing with httpie
results in success.
$ sudo dnf install httpie
$ http --body "http://localhost:7070/x/touche?go-get=1"
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="scale.dev/x/migrate git https://codeberg.org/Gusted/mCaptcha.git">
<meta name="go-source" content="scale.dev/x/migrate https://codeberg.org/Gusted/mCaptcha.git https://codeberg.org/Gusted/mCaptcha.git/tree/main{/dir} https://codeberg.org/Gusted/mCaptcha.git/blob/main{/dir}/{file}#L{line}">
</head>
</html>
Attempt Four
So I was not happy with the amount of configuration needed to get this working. So I decided to just write a system service instead because it seemed like the path of least resistance. So I ran these commends:
$ echo $PATH
/home/vagrant/.asdf/shims:/home/vagrant/.asdf/bin:/home/vagrant/.local/bin:/home/vagrant/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
$ sudo go <gocustomurls_binary> /usr/local/bin # to put in the PATH
$ sudo mkdir -p /var/lib/$USER
$ sudo cp gocustomurls.service /etc/systemd/system/
$ sudo mkdir -p /var/lib/$USER
$ sudo useradd --system --comment 'Go Custom Urls Service' --no-create-home $USER
useradd: failed to reset the lastlog entry of UID 992: No such file or directory
$ sudo passwd -d $USER
passwd: password changed.
$ getent passwd $USER
$USER:x:992:991:Go Custom Urls Service:/home/$USER:/bin/bash
$ sudo cp config.json /var/lib/$USER
$ sudo cp rules.json /var/lib/$USER
$ sudo ls /var/lib/$USER/
config.json rules.json
$ sudo chmod -R 770 /var/lib/$USER
$ sudo chown -R $USER:$USER /var/lib/$USER
$ sudo systemctl daemon-reload
$ sudo systemctl start gocustomurls.service
$ sudo systemctl status gocustomurls.service
● gocustomurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/etc/systemd/system/gocustomurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf
Active: active (running) since Sat 2024-07-20 06:52:09 UTC; 23s ago
Main PID: 4020 (gocustomurls)
Tasks: 6 (limit: 2319)
Memory: 7.1M (peak: 7.5M)
CPU: 14ms
CGroup: /system.slice/gocustomurls.service
└─4020 /usr/local/bin/gocustomurls -conf /var/lib/gourls/config.json
$ http --body "http://localhost:7070/x/touche?go-get=1"
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="scale.dev/x/migrate git https://codeberg.org/Gusted/mCaptcha.git">
<meta name="go-source" content="scale.dev/x/migrate https://codeberg.org/Gusted/mCaptcha.git https://codeberg.org/Gusted/mCaptcha.git/tree/main{/dir} https://codeberg.org/Gusted/mCaptcha.git/blob/main{/dir}/{file}#L{line}">
</head>
</html>
Attempt Five (Docker)
So this application generates logs and I wanted to view the logs outside the container. So the application logs generated as I run the container would have to have the same user as the user invoking the docker
command to avoid me having to use sudo
to view it. Based on the above, the sample docker file would be something like this
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS build
WORKDIR /app
RUN apk --update add --no-cache git bash make alpine-sdk g++ && \
git clone https://git.iratusmachina.com/iratusmachina/gocustomurls.git --branch feature/add-log-rotation gurls && \
cd gurls && make build
FROM alpine AS run
ARG USERNAME
ARG UID
ARG GID
ARG PORT
RUN mkdir -p /home/$USERNAME
COPY --from=build /app/gurls/artifacts/gocustomurls /home/$USERNAME/gocustomurls
RUN addgroup -g ${GID} ${USERNAME} && \
adduser --uid ${UID} -G ${USERNAME} --gecos "" --disabled-password --home "/home/$USERNAME" --no-create-home $USERNAME && \
chmod -R 755 /home/$USERNAME && \
chown -R ${UID}:${GID} /home/$USERNAME
ENV HOME=/home/$USERNAME
WORKDIR $HOME
EXPOSE $PORT
USER $USERNAME
CMD ["sh","-c", "/home/${USERNAME}/gocustomurls", "-conf", "${HOME}/config.json"] # based on this link https://github.com/moby/moby/issues/5509#issuecomment-1962309549
So running these set of commands produces an error:
$ docker build -t testimage:alpine --build-arg UID=$(id -u) --build-arg GID=$(id -g) --build-arg USERNAME=$(whoami) --build-arg PORT=7070 --no-cache .
...
=> => naming to docker.io/library/testimage:alpine
$ docker run -d -p 7070:7070 -v ${PWD}:/home/$(whoami) testimage:alpine
d7f5011b4e7f6317757a3ee8fe76fe710f8fd5ed1ac6a6e5c32ad5393c78f4c0
$ http --body "http://localhost:7070/x/touche?go-get=1"
http: error: ConnectionError: HTTPConnectionPool(host='localhost', port=7070): Max retries exceeded with url: /x/touche?go-get=1 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f986fc73410>: Failed to establish a new connection: [Errno 111] Connection refused')) while doing a GET request to URL: http://localhost:7070/x/touche?go-get=1
So checking the logs,
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d7f5011b4e7f testimage:alpine "sh -c /home/${USERN…" 56 seconds ago Exited (127) 55 seconds ago frosty_leakey
$ docker logs d7f5011b4e7f
-conf: line 0: /home//gocustomurls: not found
So I attempted to fix the above error by putting the CMD
command in a start.sh
file by doing this:
# syntax=docker/dockerfile:1
...
RUN echo "${HOME}/gocustomurls -conf ${HOME}/config.json" > start.sh \
&& chown ${UID}:${GID} start.sh \
&& chmod u+x start.sh
EXPOSE $PORT
USER $USERNAME
CMD ["sh", "-c", "./start.sh" ]
This also produced an error, after building and running the container as shown below:
$ docker logs e9abe2bed4b1
sh: ./gocustomurls: not found
This perplexed me. So I decided to print the environment with printenv
and run ls
in the start.sh
file. So after doing that, building and re-running the container, I got this:
$ docker logs 98dd740dc727
HOSTNAME=98dd740dc727
SHLVL=2
HOME=/home/vagrant
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
total 12K
drwxr-xr-x 2 vagrant vagrant 61 Jul 21 21:30 .
drwxr-xr-x 1 root root 21 Aug 3 14:06 ..
-rw-r--r-- 1 vagrant vagrant 2.4K Aug 3 14:06 Dockerfile
-rw-r--r-- 1 vagrant vagrant 131 Jul 21 21:18 config.json
-rw-r--r-- 1 vagrant vagrant 471 Jul 21 18:40 rules.json
This shows that the gocustomurls
binary was being deleted by the time I wanted to run a container from the image. This was confirmed here 12. So I instead used the approach shown here 13. There is also using a data-only volume but that is for another day. So after reading 13 and 14, I came up with new Dockerfile.
# syntax=docker/dockerfile:1.4
FROM golang:1.20-alpine AS build
WORKDIR /app
RUN <<EOF
apk --update add --no-cache git bash make alpine-sdk g++
git clone https://git.iratusmachina.com/iratusmachina/gocustomurls.git --branch feature/add-log-rotation gurls
cd gurls
make build
EOF
FROM alpine AS run
ARG USERNAME
ARG UID
ARG GID
ARG PORT
RUN <<EOF
addgroup --gid ${GID} ${USERNAME}
adduser --uid ${UID} --ingroup ${USERNAME} --gecos "" --disabled-password --home "/home/$USERNAME" $USERNAME
chmod -R 755 /home/$USERNAME
chown -R ${UID}:${GID} /home/$USERNAME
EOF
COPY --from=build /app/gurls/artifacts/gocustomurls /home/$USERNAME/gocustomurls
RUN <<EOF
mkdir -p /var/lib/apptemp
cp /home/$USERNAME/gocustomurls /var/lib/apptemp/gocustomurls
chown -R ${UID}:${GID} /home/$USERNAME
chown -R ${UID}:${GID} /var/lib/apptemp
ls -lah /home/$USERNAME
EOF
ENV HOME=/home/$USERNAME
COPY <<EOF start.sh
#!/usr/bin/env sh
printenv
cd "${HOME}"
ls -lah .
if [ ! -f "${HOME}/gocustomurls" ]
then
cp /var/lib/apptemp/gocustomurls ${HOME}/gocustomurls
rm /var/lib/apptemp/gocustomurls
fi
${HOME}/gocustomurls -conf ${HOME}/config.json
EOF
RUN <<EOF
chown ${UID}:${GID} start.sh
chmod u+x start.sh
EOF
EXPOSE $PORT
USER $USERNAME
CMD ["sh", "-c", "./start.sh" ]
I also changed the run command from
$ docker run -d -p 7070:7070 -v ${PWD}:/home/$(whoami) testimage:alpine
to
$ docker run -d -p 7070:7070 -v ${PWD}/otherfiles:/home/$(whoami) testimage:alpine
This allows me to exclude the Dockerfile from the image. I also added journald
logging to the run command so I can use journalctl
to view the logs. Running with this addition is shown below:
$ docker run --log-driver=journald -d -p 7070:7070 -v ${PWD}/otherfiles:/home/$(whoami) testimage:alpine
$ sudo journalctl CONTAINER_NAME=vigilant_mclaren
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: HOSTNAME=561944ae05ca
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: SHLVL=2
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: HOME=/home/vagrant
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: PWD=/
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: total 9M
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: drwxr-xr-x 2 vagrant vagrant 63 Aug 3 14:36 .
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: drwxr-xr-x 1 root root 21 Aug 3 14:29 ..
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: -rw-r--r-- 1 vagrant vagrant 131 Jul 21 21:18 config.json
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: -rwxr-xr-x 1 vagrant vagrant 9.3M Aug 3 14:31 gocustomurls
Aug 03 14:37:19 fedoramachine 561944ae05ca[1228]: -rw-r--r-- 1 vagrant vagrant 471 Jul 21 18:40 rules.json
This works. So I wanted to see if I could run this Dockerfile from a non-root, non-login account. After moving the requiste files, I ran the build command
bash-5.2$ docker build -t testimage:alpine --build-arg UID=$(id -u) --build-arg GID=$(id -g) --build-arg USERNAME=$(whoami) --build-arg PORT=7070 --no-cache --progress=plain .
ERROR: mkdir /home/golsuser: permission denied
I am using the --progress=plain
option to allow me to use echo
during the build process. This error is because docker
binary cannot find the .docker
directory for this user. Normally, this folder is auto-created when you run docker build
. So to remove this error, I ran this instead.
bash-5.2$ HOME=${PWD} docker build -t testimage:alpine --build-arg UID=$(id -u) --build-arg GID=$(id -g) --build-arg USERNAME=$(whoami) --build-arg PORT=7070 --no-cache --progress=plain .
This worked. Now I normally run my dockerfiles using Docker Compose. So a sample compose file for the Dockerfile is shown below:
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
- USERNAME=${USERNAME}
- UID=${USER_ID}
- GID=${GROUP_ID}
- PORT=${PORT:-7070}
labels:
- "maintainer=iratusmachina"
image: appimage/gocustomurls
logging:
driver: journald
container_name: app_cont
ports:
- "7070:7070"
volumes:
- ${PWD}/otherfiles:/home/${USERNAME}
I also wanted to run the compose file using systemd. So the systemd unit file is shown below:
[Unit]
Description=GocustomUrls. A custom url mapper for go packages!
After=docker.service
PartOf=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/var/lib/golsuser
User=golsuser
Group=golsuser
ExecStart=/bin/bash -c "HOME=/var/lib/golsuser USER_ID=$(id -u) GROUP_ID=$(id -g) USERNAME=$(whoami) docker compose --file /var/lib/golsuser/docker-compose.yml up --detach --force-recreate"
ExecStop=/bin/bash -c "HOME=/var/lib/golsuser USER_ID=$(id -u) GROUP_ID=$(id -g) USERNAME=$(whoami) docker compose --file /var/lib/golsuser/docker-compose.yml down -v"
SyslogIdentifier=gurls
StandardError=journal
[Install]
WantedBy=multi-user.target
So running this service produced this output:
$ sudo systemctl start gurls.service
$ sudo systemctl status gurls.service
● gurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/etc/systemd/system/gurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf
Active: active (exited) since Sat 2024-08-03 18:08:46 UTC; 50s ago
Process: 24117 ExecStart=/bin/bash -c HOME=/var/lib/golsuser USER_ID=$(id -u) GROUP_ID=$(id -g) USERNAME=$(whoami) docker compose --file /var/lib/golsuser/docker-compose.yml up --detach --force-recreate (code=exited, status=0/SUCCESS)
Main PID: 24117 (code=exited, status=0/SUCCESS)
CPU: 125ms
Aug 03 18:08:45 fedoramachine gurls[24132]: #17 DONE 0.0s
Aug 03 18:08:45 fedoramachine gurls[24132]: #18 [app] resolving provenance for metadata file
Aug 03 18:08:45 fedoramachine gurls[24132]: #18 DONE 0.0s
Aug 03 18:08:45 fedoramachine gurls[24132]: Network golsuser_default Creating
Aug 03 18:08:45 fedoramachine gurls[24132]: Network golsuser_default Created
Aug 03 18:08:45 fedoramachine gurls[24132]: Container app_cont Creating
Aug 03 18:08:45 fedoramachine gurls[24132]: Container app_cont Created
Aug 03 18:08:45 fedoramachine gurls[24132]: Container app_cont Starting
Aug 03 18:08:46 fedoramachine gurls[24132]: Container app_cont Started
Aug 03 18:08:46 fedoramachine systemd[1]: Finished gurls.service - GocustomUrls. A custom url mapper for go packages!.
$ sudo systemctl stop gurls.service
$ sudo systemctl status gurls.service
○ gurls.service - GocustomUrls. A custom url mapper for go packages!
Loaded: loaded (/etc/systemd/system/gurls.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf
Active: inactive (dead)
Aug 03 18:08:46 fedoramachine systemd[1]: Finished gurls.service - GocustomUrls. A custom url mapper for go packages!.
Aug 03 18:10:10 fedoramachine systemd[1]: Stopping gurls.service - GocustomUrls. A custom url mapper for go packages!...
Aug 03 18:10:10 fedoramachine gurls[24402]: Container app_cont Stopping
Aug 03 18:10:20 fedoramachine gurls[24402]: Container app_cont Stopped
Aug 03 18:10:20 fedoramachine gurls[24402]: Container app_cont Removing
Aug 03 18:10:20 fedoramachine gurls[24402]: Container app_cont Removed
Aug 03 18:10:20 fedoramachine gurls[24402]: Network golsuser_default Removing
Aug 03 18:10:20 fedoramachine gurls[24402]: Network golsuser_default Removed
Aug 03 18:10:20 fedoramachine systemd[1]: gurls.service: Deactivated successfully.
Aug 03 18:10:20 fedoramachine systemd[1]: Stopped gurls.service - GocustomUrls. A custom url mapper for go packages!.
Conclusion
This was a fun yet frustruating experience in trying to piece together how to run services with systemd. I am glad that I went through it so I can reference it next time. I decided to go with the systemd approach (attempt four). With go artifacts being a single binary, the overhead of managing Docker was not worth it for me. If I wanted to remove the systemd service, I could use the commands from this resource10.