I recently asked the best way to run my Lemmy bot on my Synology NAS and most people suggested Docker.

I’m currently trying to get it running on my machine in Docker before transferring it over there, but am running into trouble.

Currently, to run locally, I navigate to the folder and type npm start. That executes tsx src/main.ts.

The first thing main.ts does is access argv to detect if a third argument was given, dev, and if it was, it loads in .env.development, otherwise it loads .env, containing environment variables. It puts those variables into a local variable that I then pass around in the bot. I am definitely not tied to this approach if there is a better practice way of doing it.

opening lines of main.ts
import { config } from 'dotenv';

let path: string;

const env = process.argv[2];
if (env && env === 'dev') {
    path = '.env.development';
} else {
    path = '.env';
}

config({
    override: true,
    path
});

const {
    ENVIROMENT_VARIABLE_1
} = process.env as Record<string, string>;

Ideally, I would like a way that I can create a Docker image and then run it with either the .env.development variables or the .env ones…maybe even a completely separate one I decide to create after-the-fact.

Right now, I can’t even run it. When I type docker-compose up I get npm start: not found.

My Dockerfile
FROM node:22
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
USER node
COPY . .
CMD "npm start"
My compose.yaml
services:
  node:
    build: .
    image: an-image-name:latest
    environment:
      - ENVIROMENT_VARIABLE_1 = ${ENVIROMENT_VARIABLE_1}

I assume the current problem is something to do with where stuff is being copied to and what the workdir is, but don’t know precisely how to address it.

And once that’s resolved, I have even less idea how to go about passing through the environment variables.

Any help would be much appreciated.

  • onlinepersona@programming.dev
    link
    fedilink
    English
    arrow-up
    0
    ·
    2 months ago

    Concerning environment variables, you can use volumes in the compose.yaml to bind mount files or directories in your container e.g

    services:
      node:
        volumes:
          - ./prod.env:/usr/src/app/.env
          - ./dev.env:/usr/src/app/.env.development
    

    @elmicha@feddit.org is probably right about the CMD. Read the documentation and learn about the two modes CMD has. Try to figure it out yourself and if you can’t, reveal the spoiler below

    What I think should work

    CMD ["npm", "start"]

    runs npm with the start argument. What you passed was like running "npm start" in your shell. It looks for a command that’s literally called “npm” “space” “start”, which of course doesn’t exist.

    Anti Commercial-AI license

    • Zagorath@aussie.zoneOP
      link
      fedilink
      English
      arrow-up
      0
      ·
      2 months ago

      Yeah I actually originally had it in the [“one”, “two”] format, but it was weird because before the arguments I actually wanted the source I got it from had put something along the lines of “sh”, “-p” (it was definitely launching into a shell of some sort, with some kind of flag, but I forget exactly what). I removed those, and at the same time removed the square brackets, but messed up by keeping the quote marks.

      Re using volumes, am I understanding this correctly:

      That would mean that when standing up the container from the built Docker image, it contains the two env files, prod and development? Is there, alternatively, an easy way to only have one in the container, and choose which in the docker-compose up command?

      • onlinepersona@programming.dev
        link
        fedilink
        English
        arrow-up
        0
        ·
        2 months ago

        It’s probably “sh” “-c” that was being used. That’s the ENTRYPOINT. CMD is passed to the ENTRYPOINT. The docs explain it well, I think.

        As for volumes, TL;DR these are mounted into the container at runtime and are not in the image. You may put the dev and production config in the image, but I’d advise against it, especially if the production config contains secrets. Then they’ll be baked into the docker image.

        long explanation

        A docker image is basically an archive of archives. Each line you put in a Dockerfile is executed and the result thereof is put into an archive with a name (hash of the contents plus maybe other stuff) and that’s called a layer. To keep the layers small, they contain only the updates to the previous layer. This is done with Copy On Write (CoW).

        FROM alpine
        RUN touch /something # new archive with one single file
        RUN apk add curl # new archive with paths to curl WITHOUT /something
        

        To run a docker image aka docker run $options $image. The archive and its layers are extracted, then a new runtime environment is created using linux kernel namespaces (process namespace, network namespace, mount / storage namespace, …), and the sum of the layers is mounted in there as a “volume”. That means the processes within the container are segregated from the host’s processes, the container’s network is also segregated, the storage / files and folders too, and so on.

        So there’s a, for lack of a better term, root/runtime volume (/) from the sum of layers. So the container only sees the the layers (plus some other fundamental stuff to run like /dev, /proc, etc.). You can mount a file into the container, a folder, a device, or even a network drive if you have the right driver installed. Mounting a file or folder is called a “bind mount”, everything else (if I’m not mistaken) is just a mount. Volumes in docker are built on top of that. The doc explains in detail what volumes are and their naming.

        In conclusion, docker image = archive, docker container = extracted archive running in linux namespaces, volume = storage that be mounted into container.

        Finally, parameterising compose files: use environment variables. You can put then into a .env beside your compose.yaml or set them before calling e.g ENVIRONMENT_VARIABLE=someValue docker compose up.

        They are “interpolated” aka used in the compose file like so

        services:
          web:
            image: "webapp:${TAG}"
        

        I recommend you read the docs on the syntax, but for your purpose I’d use something like

        volumes:
          - ${ENV_FILE:?Env file not provided}:/usr/src/app/.env
        

        Then you can ENV_FILE=prod.env docker compose up or put the env var in .env and docker compose will pick it up.

        Anti Commercial-AI license