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
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.
Pipeline flow:
- CI builds a complete production image (including dependencies, compiled assets, optimized Laravel).
- CI pushes that final image to a container registry (GitHub Container Registry, Docker Hub, etc.).
- Production server pulls the pre-built image and runs it.
This is the “immutable image” model (the industry standard today).
Pipeline flow:
-
The Dockerfile contains at least two stages:
- build stage (for composer install, npm build, etc.)
- production stage (copies the build artifacts)
-
Production server itself runs
docker buildto build the final image on-device.
This is the “build on the server” model (older, less preferred now).
Both are correct and both are used in the real world. But they solve different problems.
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.
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:
- Production server never builds — only pulls.
- Build reproducibility is guaranteed (CI is controlled environment).
- Faster deployments (pull image <4 seconds).
- No build tools on production (no compiler, node, composer).
- Better security (supply-chain standard).
- Guaranteed immutability (image is frozen).
This is the professional way for all large-scale systems.
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.
docker cp storage/database.sql trc_for_prod-mysql-trc-1:/tmp/database.sqlcopy from local comp to docker container
- 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.
volumes:
- /var/www/html/node_modules
- /var/www/html/public/fontsThese 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
. 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.
- Bridge - make internal docker network that connects services inside of docker compose. 99% cases use it.
- 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.
- 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
I created a symlink and changed volume, for this you should recreate a container as follow:
docker-compose up --force--recreate -dIt 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.
RUN executes commands while building the image.
The result is baked inside the image.
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curlAfter building, the final image already contains curl.
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.”
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.
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
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.”
ENTRYPOINT defines the main executable that will always run.
You cannot override ENTRYPOINT easily unless you use --entrypoint.
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
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.”
- ENTRYPOINT = executable
- CMD = default arguments
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
| 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 |
- 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 and Portainer installation on ubuntu 22. Optional: how to change default docker root folder (where images and containers are stored)
tutorial video tested it works To install on Ubuntu 22 you should type these commands in your CLI
$ curl -fsSl https://get.docker.com -o get-docker.sh$ sudo sh get-docker.sh$ sudo groupadd docker$ 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.- logout from system then log in back
$ sudo curl -SL https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose$ sudo chmod +x /usr/local/bin/docker-compose
$ 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
$ mkdir WHEN_YOU_WANT_NEW_ROOT$ sudo nano /etc/docker/daemon.json
inside
{
"data-root": "/WHEN_YOU_WANT_NEW_ROOT"
}
$ sudo systemctl restart docker$ docker info -f '{{ .DockerRootDir}}'