Skip to content

Instantly share code, notes, and snippets.

@MuhammadQuran17
Last active March 13, 2026 10:15
Show Gist options
  • Select an option

  • Save MuhammadQuran17/0c0d13201dd0b61c2d6e6fb66de2df02 to your computer and use it in GitHub Desktop.

Select an option

Save MuhammadQuran17/0c0d13201dd0b61c2d6e6fb66de2df02 to your computer and use it in GitHub Desktop.
Docker helper

Importants

You can't COPY a file from outside the build context. So if you are 
trying to COPY /opt/venv/lib/python3.7/site-packages/xxx/resources/abc.py into your docker image, and 
that is not in your build context, it will fail. Full stop.

docker inspect coolify-proxy -f '{{ json .Config }}' | jq

Entrypoint

The command exec "$@" is necessary ONLY when you are using an ENTRYPOINT script as a wrapper around your container's primary process (CMD). It performs two key functions. exec "$@" is generally not required for your dedicated migrator service when you set the command explicitly in docker-compose.yml.

The Critical Error: Missing exec

The Problem: Docker containers run as long as the main process is running. Your entrypoint.sh runs a bunch of PHP commands and then stops. It does not hand over control to the CMD defined in your Dockerfile (which is php-fpm or the queue:work command).

The Result: Your container will start, run migrations, say "Maintenance finished," and then the container will shut down. Docker will see it stopped and restart it (due to restart: unless-stopped), causing an infinite loop of migrations.

The Fix: You must add exec "$@" at the end of the script. This tells the script to execute the command passed by the Dockerfile (php-fpm or queue:work) as the main process PID 1.

More at the end of this .md


Approach A — Pre-build an image in CI and push to a registry

Pipeline flow:

  1. CI builds a complete production image (including dependencies, compiled assets, optimized Laravel).
  2. CI pushes that final image to a container registry (GitHub Container Registry, Docker Hub, etc.).
  3. Production server pulls the pre-built image and runs it.

This is the “immutable image” model (the industry standard today).


Approach B — Use a multi-stage Dockerfile and build on the server

Pipeline flow:

  1. The Dockerfile contains at least two stages:

    • build stage (for composer install, npm build, etc.)
    • production stage (copies the build artifacts)
  2. Production server itself runs docker build to build the final image on-device.

This is the “build on the server” model (older, less preferred now).


2. Which Approach Is Correct?

Both are correct and both are used in the real world. But they solve different problems.


3. Which Is Easier?

Approach B (build on server) is easier for beginners

Because:

  • You only write one Dockerfile.
  • No CI pipeline is required.
  • Your production server does everything automatically.

But has drawbacks:

  • Slow builds.
  • Requires full build toolchain on server.
  • Higher resource usage.
  • More moving parts in production.

This method is common for very small teams or internal apps.


4. Which Is Preferable or Industry-Standard?

Approach A (build in CI and push image) is the modern default.

It is used by:

  • GitHub Actions
  • GitLab CI
  • Bitbucket Pipelines
  • AWS ECS
  • Kubernetes everywhere
  • Cloudflare Workers with Docker images
  • DigitalOcean/Kamatera/Hetzner with container platforms

Why it is preferable:

  1. Production server never builds — only pulls.
  2. Build reproducibility is guaranteed (CI is controlled environment).
  3. Faster deployments (pull image <4 seconds).
  4. No build tools on production (no compiler, node, composer).
  5. Better security (supply-chain standard).
  6. Guaranteed immutability (image is frozen).

This is the professional way for all large-scale systems.

A Docker image’s size (for example, 156 MB) is stored on disk (SSD/HDD) — NOT in RAM.

Alpine (35MB) is better and smaller then php-8.4-fpm 165MB but it requires manuall setup. However this size will get place of SSD not RAM. Alpine requires the changes, apt doesn't work for example. If you need the speed of deployment, build then use it, otherwise normal is good

Therefore:

A 200 MB image and a 35 MB image will use exactly the same RAM, as long as they run the same processes.

The only difference is:

  • Disk usage (SSD)

  • Download speed

  • Deployment time

  • Build speed

But NOT memory usage.

Commands

  1. docker cp storage/database.sql trc_for_prod-mysql-trc-1:/tmp/database.sql copy from local comp to docker container

Fix

  1. If you have problems to connect WSL to docker, and if it worked for monthes, and suddenly it stopped to work, then check in Docker Desktop > Settings > Resources > WSL integration ; section, maybe toggle for Ubuntu was turned off somehow. Just turn on.

Binding

volumes:
      - /var/www/html/node_modules
      - /var/www/html/public/fonts

These volumes prevent certain directories from being overwritten when using Docker bind mounts (-v). Here’s why:

1️⃣ /var/www/html/node_modules If you are using Node.js inside the container, the node_modules folder is generated inside the container during npm install. When mounting your local Laravel project (/var/www/html), Docker replaces the container’s directory with your local code. This means that if node_modules is not in a volume, it would get overwritten or deleted when mounting your local project. The volume ensures node_modules remains inside the container and is not affected by the local file system.

Bind Mount it is volume for your codebase into containers folder

volumes:
      - ./path/to/your/code/relative/to/dockercompose/yml : /var/ww/html

Named Volume are managed by Docker. To inspect use command docker volume inspect <name>

volumes:
     - myapplication

Ports Expose

. The Difference between EXPOSE and ports It is crucial to understand that these two commands do very different things:

Dockerfile EXPOSE: This is essentially documentation. It tells Docker (and humans) that the container listens on port 9000. However, it does not actually publish the port to your host machine. It just makes port 9000 available to other containers inside the same internal Docker network.

Compose ports (9000:9000): This acts as a bridge. It takes port 9000 inside the container and binds it to port 9000 on your host machine (your laptop or server). This opens a hole in the firewall, allowing traffic from outside Docker to enter.

Networks

  1. Bridge - make internal docker network that connects services inside of docker compose. 99% cases use it.
  2. Host - straighforward mapped to host (servers) ports. When: Ultra-high network performance and minimal latency (e.g., network quirks or high-load monitoring systems). You have non-standard protocols that don't work well with NAT (e.g., multicast, certain VPN tunnels). An example where host is useful: You're running Prometheus Node Exporter and want it to read the host's network data directly.
  3. None - full isolation from everything. Very rare.

To add container to existing network use: containers are live and running and we should add container to network with : docker network connect my-custom-network my-running-container

Up VS Start

I created a symlink and changed volume, for this you should recreate a container as follow:

docker-compose up --force--recreate -d

It doesn't worked, so I just restarted docker engine and write docker-compose up and everything works

  • docker compose up: This command builds, (re)creates, starts, and attaches to containers for a service. It is typically used to start an application for the first time or when there have been changes to the docker-compose.yml file or Dockerfile. If the images don't exist locally, it will build them. It also creates networks and volumes as defined in the compose file. By default, it runs in attached mode, displaying the logs of the containers. Using the -d flag will run it in detached mode, starting the containers in the background.
  • docker compose start: This command is used to restart containers that were previously created but have been stopped. It does not create new containers or rebuild existing ones. It is useful for bringing stopped services back online without recreating them

You only need to rebuild the image using docker compose up --build if you make changes that affect the image itself, such as: Modifying a Dockerfile. Otherwise if you have changed somthing in docker-compose.yml or in .env just docker-compose down and up

You should use down and up when you have changed the configuration in your docker-compose.yml file, since stop and start will not apply these changes

Below is a clear, simple, and practical explanation of ENTRYPOINT, CMD, and RUN in Docker, with minimal theory and exactly when to use each.


1. RUN

What it does

RUN executes commands while building the image. The result is baked inside the image.

Simple Example

FROM ubuntu:22.04

RUN apt-get update
RUN apt-get install -y curl

After building, the final image already contains curl.

When to use

Use RUN when you need to:

  • Install packages
  • Create directories
  • Copy configuration files
  • Compile assets
  • Execute any command that prepares the image

Analogy: RUN is “installing software on a laptop before giving it to someone.”


2. CMD

What it does

CMD specifies the default command that runs when the container starts.

But: the user can override CMD easily by passing another command when running the container.

Simple Example

FROM node:18

CMD ["node", "app.js"]

Running:

docker run myapp

Runs: node app.js

But this will override CMD:

docker run myapp bash

Now it runs: bash

When to use

Use CMD when:

  • You want to define a default command
  • You expect the user to override it sometimes
  • The application has a simple entry point (e.g., node app.js)

Analogy: CMD is a recommendation: “If you don’t specify something else, run this.”


3. ENTRYPOINT

What it does

ENTRYPOINT defines the main executable that will always run. You cannot override ENTRYPOINT easily unless you use --entrypoint.

Simple Example

FROM python:3.12

ENTRYPOINT ["python", "script.py"]

Running:

docker run myapp

Always runs: python script.py

If you pass arguments:

docker run myapp arg1 arg2

Docker runs:

python script.py arg1 arg2

When to use

Use ENTRYPOINT when:

  • You want the container to behave like a single-purpose tool
  • You want arguments appended, not replacing the command
  • You do NOT want users to replace the executable by mistake

Perfect for CLI tools (e.g., Terraform, FFmpeg, custom worker scripts).

Analogy: ENTRYPOINT is saying “This container IS this program.”


4. ENTRYPOINT + CMD (best practice for flexible tools)

They work together:

  • ENTRYPOINT = executable
  • CMD = default arguments

Example

FROM ubuntu

ENTRYPOINT ["ping"]
CMD ["google.com"]

Running:

docker run myping

Executes:

ping google.com

Override CMD:

docker run myping yahoo.com

Runs:

ping yahoo.com

When to use what (summary)

Use case RUN CMD ENTRYPOINT ENTRYPOINT + CMD
Install software inside image Yes No No No
Default command that can be overridden No Yes No No
Force a specific main program No No Yes Yes (ENTRYPOINT)
Provide default args for ENTRYPOINT No Yes Yes Yes
CLI-style tool No Maybe Yes Yes
Application server (Laravel, Node, etc.) Build steps Yes Sometimes Rare

Ultra-simple explanation

  • RUN = install things into the image (during build)
  • CMD = default command (can be overridden)
  • ENTRYPOINT = mandatory command (cannot be overridden easily)

If you want, I can write examples for Laravel, PHP-FPM, Queue Worker, or custom CLI tool Dockerfiles, tailored to your setup.

Docker Engine, Compose, Portainer on Ubuntu 22

Docker Engine, Compose and Portainer installation on ubuntu 22. Optional: how to change default docker root folder (where images and containers are stored)

Install docker engine

tutorial video tested it works To install on Ubuntu 22 you should type these commands in your CLI

  1. $ curl -fsSl https://get.docker.com -o get-docker.sh
  2. $ sudo sh get-docker.sh
  3. $ sudo groupadd docker
  4. $ sudo usermod -aG docker ${USER}
    where ${USER} is a variable provided by UNIX-like operating systems that contains the name of the current user, this allows the current user's name to be automatically inserted into the command so that we can add it to the "docker" group.
  5. logout from system then log in back

Install docker compose

official web-site

  1. $ sudo curl -SL https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
  2. $ sudo chmod +x /usr/local/bin/docker-compose

Optional (Not required) Portainer WEB UI For docker

official web

$ docker run -d -p 8005:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest --http-enabled

Also check Dockage as a simple and powerful alternative of Portainer

Optional (Not required) Change default docker_root folder

tutorial web

  1. $ mkdir WHEN_YOU_WANT_NEW_ROOT
  2. $ sudo nano /etc/docker/daemon.json

inside

{ 
   "data-root": "/WHEN_YOU_WANT_NEW_ROOT"
}
  1. $ sudo systemctl restart docker
  2. $ docker info -f '{{ .DockerRootDir}}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment