I want to build a tiny container image from scratch using Buildah to run a Go app.
Apart from the app itself, what other libraries etc need to be included. I am thinking that glibc is needed - is there anything else?
So in summary, I think I am asking "what are all the external dependencies that a compiled Go app needs on Linux?"
#Dave C gave the information to correctly answer this. Using ldd with the test app returned:
[bryon#localhost resttest]$ ldd restest
linux-vdso.so.1 (0x00007fff139fe000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbad6ce2000)
libc.so.6 => /lib64/libc.so.6 (0x00007fbad691f000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbad6f02000)
[bryon#localhost resttest]$
So for those looking to build a minimal container with Buildah, the BASH script to generate it would look like this:
#!/bin/bash
#
# Run this shell script after you have run the command: "buildah unshare"
#
git clone https://github.com/bryonbaker/resttest.git
cd resttest
go build restest.go
container=$(buildah from scratch)
mnt=$(buildah mount $container)
mkdir $mnt/bin
mkdir $mnt/lib64
buildah config --workingdir /bin $container
buildah copy $container restest /bin/restest
buildah copy $container /lib64/libpthread.so.0 /lib64
buildah copy $container /lib64/libc.so.6 /lib64
buildah copy $container /lib64/ld-linux-x86-64.so.2 /lib64
buildah config --port 8000 $container
#
# This step is not working properly.
# Need to run with podman -p 8000:8000 --entrypoint /bin/restest restest:latest
buildah config --entrypoint /bin/restest $container
buildah commit --format docker $container restest:latest
This generates a 14MB container for a simple microservice! There are no additional files to be worrying about for vulnerabilities etc.
I have a small defect I can't work out on entrypoints so I am overriding the entrypoint on start, but to test it run:
podman -p8000:8000 --entrypoint /bin/restest restest:latest
Then just type the following in a Terminal session:
curl http://localhost:8000
So thanks Dave C!
I know this is quite late answer, but it does tell how to build the slimmiest image for Golang programs. It is based on the question Deployment using image from scratch fails to start
The trick is to build statically linked executable and place it into the empty image called scratch. The image contains just a single file, that exact executable. It is the smallest image possible.
Docker file:
FROM golang:latest as builder
# The Dockerfile expects the source code of the application
# to reside in ./src/ directory
COPY src /src
WORKDIR /src
# Build statically linked file and strip debug information
# The Dockerfile expects the `main` package to be at the root of the module
RUN CGO_ENABLED=0 go build -ldflags="-extldflags=-static -s -w" -o executable
# scratch is an empty image
FROM scratch
# If you need /bin/sh and a few utilities, uncomment
# the following line. It increases the image by 5.5 MB
# FROM alpine:latest
COPY --from=builder /src/executable /executable
# copy other files if needed
ENTRYPOINT ["/executable"]
The Dockerfile expects the source code to be in src directory
<project_root>
|_ Dockerfile
|_ src/
|_ go.mod
|_ package_main.go # file with `package main` and `func main()`
|_ other source files
The command docker build ./ -t my-minimal-go produces the image named my-minimal-go:latest
To prove that it is the minimal image, save it to TAR and study the contents:
docker image save my-minimal-go:latest > my-minimal-go.tar
tar tf my-minimal-go.tar
The contents is something like
84ebda22f9b32043fdcb7bb70c559f0ee91cac60b4b92f1ce424662afec6d4b9.json
e622775ad65d50bc0b9f30e6ce58ee7670f752c63c3ca70caba4f9165efdca80/
e622775ad65d50bc0b9f30e6ce58ee7670f752c63c3ca70caba4f9165efdca80/VERSION
e622775ad65d50bc0b9f30e6ce58ee7670f752c63c3ca70caba4f9165efdca80/json
e622775ad65d50bc0b9f30e6ce58ee7670f752c63c3ca70caba4f9165efdca80/layer.tar
manifest.json
repositories
And to see the list of files in the image:
docker image save my-minimal-go:latest | tar x --wildcards '*layer.tar' -O | tar t
Output:
executable
Just a single file, the minimal image.
I am assuming you have included the app dependencies in your docker image.
You won't require any external dependency to build a docker image. Just base image from Go is sufficient to build and run on Linux machines.
# Start from the latest Go base image
FROM golang:latest
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
Related
I would like to build and image from Dockerfile using Earthly.
You might be wondering why do I want that, because I can describe images right inside of Earthfile, but I have 2 reasons for using external Dockerfile:
ADD command (which I need to download file by URL) is not supported by Earthly yet
I would like to use a heredoc syntax for embedding file's content into container right from Dockerfile. This requires # syntax=docker/dockerfile:1.4, which is again not available in Earthfile
So, here is what I tried to do.
My approximate Dockerfile looks like:
# syntax=docker/dockerfile:1.4
FROM gcr.io/distroless/java17:nonroot
WORKDIR /opt/app
ADD --chown=nonroot https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.4.7/applicationinsights-agent-3.4.7.jar agent.jar
COPY <<EOF /opt/app/applicationinsights.json
{
"instrumentation": {}
}
EOF
And this is how I try to build it with Earthly:
base-image:
FROM earthly/dind:alpine
WORKDIR /build
ENV DOCKER_BUILDKIT=1 # <---- required to support heredoc syntax
COPY distroless-runtime-17.Dockerfile Dockerfile
WITH DOCKER --allow-privileged
RUN docker build . -t base-17-image
END
While the WITH DOCKER RUN part gets executed successfully, I do not know how to use the result of base-image target in other targets to package my app using the resulting base image. The FROM base-17-image just fails as if it does not exist (and this tag really does not exist - docker run base-17-image fails with the same reason).
It turned out to be very easy and natively supported:
The whole recipe is just 2 lines of code:
base-image:
FROM DOCKERFILE -f distroless-runtime-17.Dockerfile .
and the result can of the above step can be reused to package your application as: FROM +base-image
I'm trying to create a dockerfile for my go server but it keeps failing as it does not recognize some local dependencies (they are modules on the code itself, not external dependencies).
example:
import (
"<private-repo-url>/src/cmd/http-api/bootstrap" // this a local module that's part of the server
"go.uber.org/fx"
)
func main() {
fx.New(bootstrap.Module).Run()
}
Here's the error:
=> ERROR [7/7] RUN go build -a -o ./server 0.3s
------
> [7/7] RUN go build -a -o ./server:
#10 0.295 server.go:4:2: no required module provides package <private-repo-url>/src/cmd/http-api/bootstrap; to add it:
#10 0.295 go get <private-repo-url>/src/cmd/http-api/bootstrap
------
executor failed running [/bin/sh -c go build -a -o ./server]: exit code: 1
Please note that this private-repo-url corresponds to this application's repository (it's not an external dependency).
Here's the Dockerfile
FROM golang:1.17
WORKDIR /balrog
# Copy dependency definitions and download them
ADD go.mod .
ADD go.sum .
RUN go mod download
# Build the binary
ADD ./src .
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
RUN go build -a -o ./server
#Run the server
CMD ["/server"]
And the mod.go file:
module <private-repo-url>
go 1.16
require (
github.com/gin-gonic/gin v1.7.7
github.com/google/uuid v1.3.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/sirupsen/logrus v1.8.1
go.uber.org/fx v1.15.0
)
I've read about GO111MODULE saying it should be on, and I also read that it's enabled by default from 1.17 (here).
Also according to the official docker image (in dockerhub) the right way is using go get and go install after copying all the files. This approach lead me to a slightly different problem which is that the docker can not access the repository (because it's private) and adding credentials to the docker is something I'd like to avoid.
I tried to play arround with the environment variable GOVCS setting it's value like:
ENV GOVCS=github.com:git,gitlab.com:off
But it still did fail with the same error.
Finally I tried with the replace, I figured that if I removed the from the local dependencies it would work, so I executed (inside the Dockerfile) this:
RUN go mod edit -replace <private-repo-url>=./
Again it did fail with:
=> ERROR [builder 10/10] RUN go build -a -o ./server 0.3s
------
> [builder 10/10] RUN go build -a -o ./server:
#17 0.299 server.go:4:2: module <private-repo-url>/src provides package <private-repo-url>/src/cmd/http-api/bootstrap and is replaced but not required; to add it:
#17 0.299 go get <private-repo-url>/src
#17 0.299 server.go:5:2: no required module provides package go.uber.org/fx; to add it:
#17 0.299 go get go.uber.org/fx
Is there any way to prevent go builder/package installer to look for these files externally? As both go mod and go get + go install try to access this private repository and fail as they do not have access. But they should not try to access it on the first place as it's the application's repository... Or is that I'm doing something wrong (clearly or I wouldn't be here), missing something?
ADD ./src . - that copies the contents of src to the current folder, stripping away the src part.
It should just be COPY . ./
Also note that it's not recommended to have a src subfolder in your source tree - the folder that contains go.mod is already the source tree.
I am trying to build a docker image on my windows machine and I keep getting this error:
[+] Building 2.1s (12/15)
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 538B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 35B 0.0s
=> [internal] load metadata for docker.io/library/node:alpine 1.0s
=> [ 1/11] FROM docker.io/library/node:alpine#sha256:6b56197d33a56cd45d1d1214292b8851fa1b91b2ccc678cee7e5fd4260bd8fae 0.0s
=> [internal] load build context 1.0s
=> => transferring context: 15.72kB 1.0s
=> CACHED [ 2/11] WORKDIR /app 0.0s
=> CACHED [ 3/11] COPY package.json . 0.0s
=> CACHED [ 4/11] COPY tsconfig.json . 0.0s
=> CACHED [ 5/11] COPY swagger.yaml . 0.0s
=> CACHED [ 6/11] COPY services . 0.0s
=> CACHED [ 7/11] RUN yarn install 0.0s
=> ERROR [ 8/11] ADD . /app 0.0s
------
> [ 8/11] ADD . /app:
------
cannot copy to non-directory: /var/lib/docker/overlay2/xw77p2bxfkhhnwqs5umpl7cbi/merged/app/.git
My Dockerfile is the following:
FROM node:alpine
#Create Directory for the Container
WORKDIR /app
#Copy the package.json and tsconfig.json to work directory
COPY package.json .
COPY tsconfig.json .
COPY swagger.yaml .
COPY services .
#Install all packages
RUN yarn install
#Copy all other source code to work directory
ADD . /app
#Build sources
RUN yarn run build
#Clean src directory
RUN rm -rf ./src/
RUN rm -rf ./services/src/
#Expose Ports
EXPOSE 3000
#Entry
CMD ["yarn", "start"]
This Dockerfile works on my colleagues' Linux machines but fails on my windows machine.
This is my Docker version
Docker version 20.10.7, build f0df350
Running on windows and using the wsl 2 to interact with it.
But the build also fails using the Windows command prompt.
Thank you in advance for the help!
Just verify the .git is actually a file or folder. Or check for any name conflict between a folder and file.
Seems like you are trying to copy a folder to a file(non-directory).
I have faced a similar issue error: failed to solve: cannot replace to directory /var/lib/docker/overlay2/*/*/folder_a with file. Turns out i have a binary file with a name 'folder_a'.
Deleting the file which matches the folder_name solved the issue for me.
My case
Destination directory not created before copy command
RUN mkdir <destination directory>
I also faced a similar issue, when trying to copy only the necessary files of my monorepo. This occurs when your are trying to copy files to a non-existent directory.
COPY package.json .
COPY ./packages/shared-ui/package.json packages/shared-ui
COPY ./packages/shared-ui packages/shared-ui
COPY ./apps/frontend/package.json apps/frontend
COPY ./apps/frontend apps/frontend
To fix this, you first need to create the apps/frontend directory & packages/shared-ui directory before copying over the source files.
COPY package.json .
/* Create these directories first */
RUN mkdir -p packages/shared-ui
RUN mkdir -p apps/frontend
COPY ./packages/shared-ui/package.json packages/shared-ui
COPY ./packages/shared-ui packages/shared-ui
COPY ./apps/frontend/package.json apps/frontend
COPY ./apps/frontend apps/frontend
The -p flag is important to include with mkdir else the command won't work.
I had a similar issue with the COPY command and resolved it by adding the directory name in the destination path as suggested in https://sumito.jp/2021/08/04/cannot-copy-to-non-directory-var-lib-docker-overlay2/
Below is my docker file.
FROM node:6-alpine
EXPOSE 3000
RUN apk add -update tini
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json package.json
RUN npm install && npm cache clean
COPY . .
CMD [ "tini","--","node","./bin/www" ]
I am running this on Windows using a command
docker build -t testnode .
getting below error
Sending build context to Docker daemon 443.4kB
Error response from daemon: failed to parse Dockerfile: file with no instructions.
There are no extra space in my file and Encoding is ANSI.
Try this out -
FROM node:6-alpine
EXPOSE 3000
RUN apk add --update tini
WORKDIR /usr/src/app
COPY package.json package.json
RUN npm install
RUN npm cache clean --force
COPY . .
ENTRYPOINT ["/sbin/tini", "--"]
CMD [ "node", "./bin/www" ]
ENTRYPOINT should be defined when using the container as an
executable.
CMD should be used as a way of defining default arguments
for an ENTRYPOINT command or for executing an ad-hoc command in a
container.
Here "node" and "./bin/www" are passed as arguments to the "/sbin/tini" executable.
Check out this stackoverflow answer for a detailed understanding of ENTRYPOINT and CMD.
I believe you are taking Docker Mastery tutorial by Bret Fisher. Happy Learning!!
The way I've been able to reproduce this error is to create a Dockerfile that only contains a comment:
$ echo '#' >df.comment
$ docker build -f df.comment .
[+] Building 0.1s (2/2) FINISHED
=> [internal] load build definition from df.comment 0.0s
=> => transferring dockerfile: 45B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 34B 0.0s
failed to solve with frontend dockerfile.v0: failed to create LLB definition: file with no instructions
Note that an empty image, without an entrypoint or cmd, will still build, so trying to fix that with those lines is going to send you the wrong way:
$ more df.scratch
FROM scratch
LABEL image=scratch
$ docker build -f df.scratch .
[+] Building 0.3s (3/3) FINISHED
=> [internal] load build definition from df.scratch 0.0s
=> => transferring dockerfile: 76B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 34B 0.0s
=> exporting to image 0.0s
=> => writing image sha256:874d031dd4b7103a9f4159a2bc82c7d21e1885aa477150105d482b3e0462aa7e 0.0s
As for why your file isn't working, it's almost certainly something to do with the encoding of the file. From the CLI rather than the editor, try cat Dockerfile and you should see the file contents, with steps like FROM at the beginning of the lines. If the file appears to be corrupt or missing line feeds, then it's not saved in the correct format. Typically you would use utf-8, ascii should also work, with linefeeds set to the Linux format (LF, not CR-LF).
To begin, this is my project hierarchy:
myproj/
- commons1/
- com1_file1.go
- ...
- commons2/
- com2_file1.go
- ...
- module1/
- mod1_file1.go
- Dockerfile
- ...
- module2/
- mod2_file1.go
- Dockerfile
- ...
- docker-compose.yml
What I'd like to do is that when module1 and module2 containers start up, they each have a copy of all the commonsN directories in their GOPATH's so that each can access the common libraries exposed by each of the commonsN directories.
For example, I would like to see something like this in the container for module1:
/go/
- src/
- commons1/
- com1_file1.go
- ...
- commons2/
- com2_file1.go
- ...
- module1/
- mod1_file1.go
- ...
Reason being is that this is basically how my local GOPATH looks (with the addition of the other modules of course) so that I can do something like this in my source files:
package main
import(
"fmt"
"myproj/commons1"
)
func main() {
fmt.Println("Some thing from common library :", commons1.SomethingFromCommons)
}
From my naive understanding of Docker, it appears I'm not allowed to modify my Dockerfiles to do something along the lines of COPY ../commons1 /go/src/commons1, so I'm wondering how I would go about accomplishing this?
I would strongly prefer to not go the Github route since the source code is all behind company proxies and whatnot and I'm imagining configuring all that is going to take way longer than simply copying some directories.
Edit
I have updated my docker-compose.yml file to look something like this per suggestion from barat:
version: '2'
services:
module1:
volumes:
- ./commons1:/go/src/myproj/commons1
build: module1/
Dockerfile for module1 looks like this:
FROM golang:1.8.0
RUN mkdir -p /go/src/app
WORKDIR /go/src/app
COPY . /go/src/app
RUN go get -d -v
RUN go install -v
ENTRYPOINT /go/bin/app
EXPOSE 8080
docker-compose build fails on the go get -d -v with error:
package myproj/commons1: unrecognized import path "myproj/commons1" (import path does not begin with hostname)
If myproj/commons1 was copied into /go/src/, then this shouldn't be an issue right? I'm guessing then it hasn't been copied over then?
You could build an image including commons1 and commons2 that your other images are based on.
FROM golang:1.8.0
RUN mkdir -p /go/src/myproj/commons1 && mkdir -p /go/src/myproj/commons2
COPY commons1/ /go/src/myproj/commons1/
COPY commons2/ /go/src/myproj/commons2/
The downside is this requires an external build step whenever you update one of the common projects:
docker build -t me/myproj:commons .
Then your compose apps can rely on the commons image instead of golang and build as normal without the volumes.
FROM me/myproj:commons
...
So problem was the go get -d -v command since it was complaining myproj/commons1 wasn't installed in $GOPATH/src basically. This I of course suspect was because Docker Compose wasn't mounting the volumes I mentioned before it ran the go get on docker-compose build so I made a work around in my docker-compose.yml but it is far from elegant:
version: '2'
services:
module1:
volumes:
- ./commons1:/go/src/myproj/commons1
build: module1/
ports:
- "8080:8080"
command: bash -c "go get -d -v && go install -v && /go/bin/app
This is obviously far from ideal because my Go binary is rebuilt every time I do a docker-compose up regardless of whether or not I ran docker-compose build.
This is also problematic because I wanted to use dockerize for certain containers to wait until another container has started up completely and it becomes quite messy now I think.