Reputation: 31
I'm trying to write notification service with gomail. When I run my app locally everything works just fine but when I dockerize my app I get nil pointer error because gomail cannot find html templates.
Error:
app_1 | panic: runtime error: invalid memory address or nil pointer dereference
app_1 | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]
Here is my dockerfile:
FROM golang@.. as builder
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
RUN adduser \
--disabled-password \
--gecos "" \
--home "/noneexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM scratch
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .
EXPOSE 8000
ENTRYPOINT ["./main"]
My folder structure:
notification-service
├── Makefile
├── README.md
├── common
│ ├── constants.go
│ ├── env.go
│ └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│ └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│ └── mail.go
├── templates
│ ├── info.html
│ └── invite.html
├── kafka
│ ├── consumer.go
│ └── producer.go
Code:
kafka/consumer.go
if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
continue
}
services/mail.go
type MailServiceInterface interface {
Send(template string, to string, subject string, data interface{}) bool
}
type Mail struct {
}
func NewMailServiceClient() MailServiceInterface {
return &Mail{}
}
func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
t, _ := m.parseTemplate(template, data)
if ok := m.sendMail(*t, to, subject); ok {
log.Println(fmt.Sprintf("Email has been sent to %s", to))
return true
} else {
log.Println(fmt.Sprintf("Failed to send the email to %s", to))
return false
}
}
func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
t, err := template.ParseFiles(templateFileName)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, data); err != nil {
return nil, err
}
body := buf.String()
return &body, nil
}
func (m *Mail) sendMail(template, to, subject string) bool {
env := common.GetEnvironment()
from := env.MailUsername
password := env.MailPassword
port, _ := strconv.Atoi(env.MailPort)
mailServer := env.MailServer
mail := gomail.NewMessage()
mail.SetHeader("From", from)
mail.SetHeader("To", to)
mail.SetHeader("Subject", subject)
mail.SetBody("text/html", template)
d := gomail.NewDialer(mailServer, port, from, password)
if err := d.DialAndSend(mail); err != nil {
zap.S().Error(err)
return false
}
return true
}
Upvotes: 0
Views: 967
Reputation: 44962
At the end of your dockerfile you run COPY --from=builder /app/main .
so you're copying only the executable.
However template.ParseFiles(templateFileName)
implementation uses os.ReadFile(file)
, which means it looks for files in the file system. Those files are not there because you copied only the executable.
If you don't want to change your build process and are on Go 1.16 or above, embed
the html files into your executable:
Folder structure:
├── templates
│ ├── info.html
│ └── invite.html
│ └── templates.go <-- new file
templates.go
package templates
import _ "embed"
//go:embed info.html
var Info string
//go:embed invite.html
var Invite string
services/mail.go
t, err := template.New("").Parse(templates.Info)
Upvotes: 5